diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md new file mode 100644 index 000000000..10dff02ae --- /dev/null +++ b/zips/draft-valargroup-shielded-voting.md @@ -0,0 +1,1722 @@ + ZIP: Unassigned + Title: Shielded Voting Protocol + Owners: Dev Ojha + Roman Akhtariev + Adam Tucker + Greg Nagy + Credits: Daira-Emma Hopwood + Jack Grigg + Status: Draft + Category: Standards + Created: 2026-03-04 + License: MIT + Pull-Request: + + +# Terminology + +The key words "MUST", "REQUIRED", "MUST NOT", "SHOULD", and "MAY" in this +document are to be interpreted as described in BCP 14 [^BCP14] when, and +only when, they appear in all capitals. + +The character § is used when referring to sections of the Zcash Protocol +Specification. [^protocol] + +The terms below are to be interpreted as follows: + +Ballot + +: A unit of voting weight derived from a zatoshi balance. The + conversion from zatoshi to ballots is defined in [Ballot Scaling]. + +Vote Authority Note (VAN) + +: A commitment inserted into the Vote Commitment Tree that represents + spendable voting authority. A VAN binds a voting hotkey, a ballot + count, a voting round identifier, and a proposal authority bitmask. + +Vote Commitment (VC) + +: A commitment inserted into the Vote Commitment Tree that binds a + voter's encrypted share distribution, proposal choice, and vote + decision for a single proposal. A VC is created when a VAN is consumed + to cast a vote. + +Vote Commitment Tree (VCT) + +: An append-only Poseidon Merkle tree maintained by the vote chain that + stores both VANs and VCs as leaves. A separate VCT is maintained per + voting round; trees from different rounds are fully isolated. + +Vote share + +: One of $N_s$ encrypted portions of a voter's ballot count within a + Vote Commitment. Each share is submitted independently for + homomorphic accumulation. + +Voting round + +: A bounded period during which a set of proposals are open for voting. + Each round is identified by a unique $\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}$ and + is associated with a pool snapshot, an election authority public key, + and a set of proposals. + +Election authority (EA) + +: The set of validators that collectively hold Shamir shares of the + El Gamal secret key $\mathsf{ea}\_\mathsf{sk}$ for a voting round. + +Submission server + +: An untrusted server to which a voter delegates the construction and + submission of Vote Reveal Proofs. + +Governance hotkey +: An Orchard key hierarchy, distinct from the holder's spending key, + that provides the key material for all voting operations after + delegation. The hotkey is generated on a general-purpose device + capable of ZKP construction. + +Governance nullifier + +: An alternate nullifier (as defined in [^balance-proof]) scoped to the + governance domain, published during delegation to prevent + double-delegation of the same Orchard note within a voting round. + +VAN nullifier + +: A nullifier derived from a VAN commitment and published when the VAN + is consumed (to cast a vote or delegate), preventing double-spending + of voting authority. + +Share nullifier + +: A nullifier derived from a vote commitment and share index, published + when a share is revealed, preventing double-counting. + + +# Abstract + +This ZIP specifies a shielded voting protocol that allows holders of +Orchard notes to cast stake-weighted votes on proposals without revealing +their identity, individual balances, or vote allocations. + +The protocol proceeds in three proving phases. First, a *delegation +proof* (building on the Orchard Proof-of-Balance [^balance-proof]) +converts proven Orchard balance into a Vote Authority Note on a +purpose-built vote chain. Second, a *vote proof* consumes a VAN to +produce a Vote Commitment containing $N_s$ El Gamal-encrypted shares of +the voter's ballot count, split across vote options. Third, a *vote +reveal proof*, constructed by an untrusted submission server, opens +individual encrypted shares for homomorphic accumulation, without +revealing which Vote Commitment the share originated from. + +After the voting window closes, anyone can publicly aggregate the +revealed El Gamal ciphertexts per proposal option. Validators holding +Shamir shares of the election authority key cooperate to produce partial +decryptions; the results are combined via Lagrange interpolation and +the aggregate total is publicly verified. Individual vote amounts are +never revealed. + + +# Motivation + +Stake-weighted voting in privacy-preserving systems faces a fundamental +tension: demonstrating voting power requires proving a balance, but +linking that balance to a vote destroys the privacy that shielded +transactions provide. + +This ZIP addresses that tension for Zcash's Orchard shielded pool. The +Orchard Proof-of-Balance [^balance-proof] provides the foundational +primitive, proving note ownership without revealing standard nullifiers. +This ZIP builds on that primitive to specify a complete voting protocol +with the following properties: + +- **Unlinkable delegation.** A holder delegates voting power to a + locally-generated hotkey via a zero-knowledge proof. The delegation + is unlinkable to the holder's on-chain identity. +- **Private vote splitting.** Votes are decomposed into El Gamal- + encrypted shares submitted independently, preventing balance + reconstruction via timing or amount analysis. +- **Homomorphic tallying.** Encrypted shares are accumulated on-chain + via component-wise point addition. Only the aggregate total per + proposal option is ever decrypted. + +The protocol is motivated by coinholder governance in the Zcash +ecosystem, where participants vote on proposals weighted by their ZEC +holdings. The same mechanism applies to any stake-weighted polling system +over an Orchard-like shielded pool. + + +# Privacy Implications + +**Unlinkability to on-chain identity.** The delegation phase moves +voting authority from the holder's Orchard spending key to an unlinkable +governance hotkey. All subsequent voting transactions use this hotkey. +An observer who sees both the governance nullifiers (published during +delegation) and the standard nullifiers (published when notes are later +spent on-chain) cannot link them without knowledge of $\mathsf{nk}$. +This follows from the alternate nullifier unlinkability property +established in [^balance-proof]. + +**Balance hiding via vote splitting.** A voter's total ballot count is +decomposed into $N_s$ shares encrypted under the election authority's +public key. Each share is submitted independently (potentially via +different submission servers at randomized delays), preventing an +observer from reconstructing the voter's total weight from individual +submissions. + +**Individual vote amounts hidden.** Each share is an El Gamal ciphertext +whose plaintext value is never revealed. Only the aggregate total per +(proposal, decision) pair is decrypted at tally time. + +**Vote commitment unlinkability.** The Vote Reveal Proof proves +that a revealed share belongs to some valid Vote Commitment in the VCT +without revealing which one. Blinded per-share commitments prevent +observers from recomputing $\mathsf{shares}\_\mathsf{hash}$ from on-chain +ciphertexts and linking revealed shares back to a specific VC. + +**Trust assumptions.** After the key ceremony, no single party holds +$\mathsf{ea}\_\mathsf{sk}$; each validator holds only a Shamir share. An +adversary must compromise at least $t$ validators (where +$t = \lceil n/2 \rceil + 1$) to reconstruct the key and decrypt +individual share ciphertexts. Even with access to the full key, privacy +against the EA relies on vote splitting: the EA would see encrypted +shares but cannot link them to specific voters or vote commitments. +Submission servers learn the encrypted share ciphertext, blind factor, +and blinded share commitments for each share they submit, along with +the proposal identifier and vote decision, but cannot decrypt plaintext +amounts or link shares to voter identities. The +primary trust requirement on submission servers is not leaking timing +metadata; using multiple independent servers with randomized delays +for submission mitigates this risk. See [^submission-server]. + +**Non-membership tree queries.** Obtaining exclusion proofs for the +nullifier non-membership tree during delegation requires querying a data +source. Private information retrieval mitigates this leakage; +see [^pir-governance]. + + +# Requirements + +- A holder's on-chain identity (spending key, standard nullifiers) is + not linkable to their voting activity. +- The number of user-facing signatures required for delegation is + minimized. +- No double-delegation for the same note within a voting round. +- No double voting for the same voting share within the same proposal. +- Individual vote amounts are not revealed at any point; only aggregate + totals per (proposal, decision) pair are recoverable. +- The aggregate tally is publicly verifiable: any party can confirm the + homomorphic accumulation. +- The delegation phase is compatible with hardware wallets that support + only the standard Orchard PCZT [^pczt] signing flow, without + requiring firmware changes specific to the voting protocol. + + +# Non-requirements + +- The consensus mechanism and operational parameters of the vote chain. +- The election authority key ceremony (generation, threshold sharing, + and distribution of $\mathsf{ea}\_\mathsf{sk}$ shares) is specified separately + in [^ea-ceremony]. +- The operational process for conducting a coinholder vote (validator + setup, poll creation, deadlines) is out of scope; it is specified + in [^voting-setup]. +- Post-quantum security of the El Gamal encryption layer is out of + scope. +- Privacy-preserving retrieval of nullifier non-membership proofs is + specified separately in [^pir-governance]. + + +# High-level summary + +This section is non-normative. + +The protocol proceeds in five phases within a voting round. + +**Phase 1: Delegation.** A holder proves ownership of unspent Orchard +notes at a pool snapshot (using the Claim circuit from [^balance-proof]) +and delegates voting authority to a locally-generated governance hotkey. +The delegation produces a Vote Authority Note (VAN) that is inserted +into the Vote Commitment Tree on the vote chain. Governance nullifiers +are published to prevent double-delegation. + +**Phase 2: Voting.** The governance hotkey consumes a VAN by publishing +its VAN nullifier, and produces two new VCT leaves: a replacement VAN +with the voted proposal's authority bit cleared, and a Vote Commitment +containing $N_s$ El Gamal-encrypted shares of the voter's ballot count. + +**Phase 3: Share submission.** The voter sends each encrypted share as +an independent payload to one or more submission +servers [^submission-server]. Each payload includes a client-chosen +$\mathsf{submit\_at}$ timestamp and the data necessary for the server +to construct a Vote Reveal Proof. If the voter is casting near the end +of the voting window (within the last-moment buffer defined +in [^submission-server]), the voter places the full ballot count into a +single share and requests immediate submission. + +**Phase 4: Share reveal.** Each submission server constructs a Vote +Reveal Proof (proving the share belongs to a valid VC in the VCT without +revealing which one) and submits it to the vote chain at the +client-specified time. The chain accumulates the revealed El Gamal +ciphertexts homomorphically. + +**Phase 5: Tally.** After the voting window closes, at least $t$ +validators produce partial decryptions of the aggregate ciphertext per +(proposal, decision) pair. The partial decryptions are stored on-chain +and combined via Lagrange interpolation to recover the total ballot +count (via the bounded discrete-log recovery procedure defined +in [^ea-ceremony]). Correctness is publicly +verifiable: anyone can recompute the Lagrange combination from the +on-chain partial decryptions. + + +# Specification + +## El Gamal Encryption on Pallas + +The protocol uses additively homomorphic El Gamal encryption over the +Pallas curve to encrypt vote share amounts. The scheme — including +setup, encryption, additive homomorphism, and decryption — is defined +in [^ea-ceremony]. This ZIP uses the notation established there: $G$ is +the Pallas $\mathsf{SpendAuthSig}^{\mathsf{Orchard}}$ +generator [^protocol-concretespendauthsig], +$\mathsf{ea}\_\mathsf{pk} = [\mathsf{ea}\_\mathsf{sk}]\, G$ is the election +authority's public key for the current voting round, and a ciphertext is +a pair of Pallas points $(C_1, C_2)$ where +$\mathsf{Enc}(v, r) = \bigl([r]\, G,\; [v]\, G + [r]\, \mathsf{ea}\_\mathsf{pk}\bigr)$. + + +## Data Structures + +### Voting Round Identifier + +Each voting round is identified by a 32-byte +$\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}$, derived +deterministically from the round's setup parameters by Poseidon +hashing. Validators and verifiers compute it from the same inputs +and check that it matches the on-chain identifier. + +$$\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}} = \mathsf{Poseidon}\bigl(\mathsf{snapshot}\_\mathsf{height},\ \mathsf{bh}\_\mathsf{lo},\ \mathsf{bh}\_\mathsf{hi},\ \mathsf{ph}\_\mathsf{lo},\ \mathsf{ph}\_\mathsf{hi},\ \mathsf{vote}\_\mathsf{end}\_\mathsf{time},\ \mathsf{nullifier}\_\mathsf{imt}\_\mathsf{root},\ \mathsf{nc}\_\mathsf{root}\bigr)$$ + +The hash uses the $\mathsf{ConstantLength}\langle 8 \rangle$ variant +of the Poseidon instantiation specified in [Poseidon Instantiation]. + +where: + +- $\mathsf{snapshot}\_\mathsf{height} \in \{ 0 .. 2^{64}-1 \}$ — the + Zcash mainnet block height of the chosen snapshot, encoded as a + Pallas base field element. +- $\mathsf{bh}\_\mathsf{lo}, \mathsf{bh}\_\mathsf{hi} \in \{ 0 .. 2^{128}-1 \}$ + — the low and high 128-bit halves of + $\mathsf{snapshot}\_\mathsf{blockhash}$ in little-endian byte + order, each encoded as a Pallas base field element. +- $\mathsf{ph}\_\mathsf{lo}, \mathsf{ph}\_\mathsf{hi} \in \{ 0 .. 2^{128}-1 \}$ + — the low and high 128-bit halves of $\mathsf{proposals}\_\mathsf{hash}$ + in little-endian byte order. +- $\mathsf{vote}\_\mathsf{end}\_\mathsf{time} \in \{ 0 .. 2^{64}-1 \}$ + — Unix timestamp (seconds) after which votes are no longer + accepted, encoded as a Pallas base field element. +- $\mathsf{nullifier}\_\mathsf{imt}\_\mathsf{root} \in \{ 0 .. q_{\mathbb{P}}-1 \}$ + — root of the nullifier non-membership IMT at the snapshot + height. MUST be a canonical Pallas base field element. +- $\mathsf{nc}\_\mathsf{root} \in \{ 0 .. q_{\mathbb{P}}-1 \}$ — + Orchard note commitment tree root at the snapshot height. MUST + be a canonical Pallas base field element. + +$\mathsf{snapshot}\_\mathsf{blockhash}$ and +$\mathsf{proposals}\_\mathsf{hash}$ are split into two 128-bit +limbs because they are not necessarily canonical Pallas field +elements. $\mathsf{nullifier}\_\mathsf{imt}\_\mathsf{root}$ and +$\mathsf{nc}\_\mathsf{root}$ are themselves Poseidon-derived in +their respective trees and are therefore already canonical. + +### Vote Authority Note (VAN) + +A VAN represents spendable voting authority on the vote chain. Its +commitment is computed in two layers: + +$$\mathsf{van}\_\mathsf{core} = \mathsf{Poseidon}\bigl(\mathsf{DOMAIN}\_\mathsf{VAN}, \mathsf{vpk}\_{\mathsf{g}\_\mathsf{d}}, \mathsf{vpk}\_{\mathsf{pk}\_\mathsf{d}}, \mathsf{num}\_\mathsf{ballots}, \mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}, \mathsf{proposal}\_\mathsf{authority}\bigr)$$ + +$$\mathsf{van} = \mathsf{Poseidon}\bigl(\mathsf{van}\_\mathsf{core}, \mathsf{gov}\_{\mathsf{comm}\_\mathsf{rand}}\bigr)$$ + +The first layer binds the structural fields; the second layer blinds +the commitment with randomness. These are two separate Poseidon +invocations ($\mathsf{ConstantLength}\langle 6 \rangle$ then +$\mathsf{ConstantLength}\langle 2 \rangle$), not a single 7-input +sponge absorption. + +where: + +- $\mathsf{DOMAIN}\_\mathsf{VAN} = 0$ — domain tag distinguishing VANs from VCs + in the shared VCT. +- $\mathsf{vpk}\_{\mathsf{g}\_\mathsf{d}} \in \{ 0 .. q_{\mathbb{P}}-1 \}$ — + $\mathsf{Extract}\_{\mathbb{P}}$ of the diversified base of the governance + hotkey address. +- $\mathsf{vpk}\_{\mathsf{pk}\_\mathsf{d}} \in \{ 0 .. q_{\mathbb{P}}-1 \}$ — + $\mathsf{Extract}\_{\mathbb{P}}$ of the diversified transmission key of the + governance hotkey address. +- $\mathsf{num}\_\mathsf{ballots} \in \{1 \ldots 2^{30}\}$ — total voting + weight in ballots. +- $\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}} \in \{ 0 .. q_{\mathbb{P}}-1 \}$ — scopes this VAN to a specific voting round. +- $\mathsf{proposal}\_\mathsf{authority} \in \{0 \ldots 2^{16}-1\}$ — bitmask + encoding which proposals this VAN is authorized to vote on. + $\mathsf{proposal}\_\mathsf{id}$ values 1–15 map to bits 1–15; bit 0 is + reserved (see [Why Proposal Identifiers Start at 1]). Full authority + is $2^{16} - 1 = 65535$. +- $\mathsf{gov}\_{\mathsf{comm}\_\mathsf{rand}} \in \{ 0 .. q_{\mathbb{P}}-1 \}$ — + commitment randomness. + +**VAN nullifier.** When a VAN is consumed (to vote or delegate), its +nullifier is: + +$$\mathsf{van}\_\mathsf{nullifier} = \mathsf{Poseidon}\bigl(\mathsf{vsk.nk}, \mathsf{tag}_{\mathsf{van}}, \mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}, \mathsf{van}\bigr)$$ + +where $\mathsf{tag}_{\mathsf{van}}$ is the field-element encoding of the domain +separator `"vote authority spend"` and $\mathsf{vsk.nk}$ is the nullifier +deriving key from the governance hotkey's full viewing key. + +A VAN MUST be created during delegation (Phase 1) and +consumed during voting (Phase 2), which MUST produce a replacement VAN +with updated $\mathsf{proposal}\_\mathsf{authority}$. + +The VAN model is +designed to support future extensions such as partial delegation +(splitting $\mathsf{num}\_\mathsf{ballots}$ across multiple delegates), +but this ZIP specifies only the delegation and voting operations. + +### Vote Commitment (VC) + +A VC commits to a vote on a specific proposal: + +$$\mathsf{vc} = \mathsf{Poseidon}\bigl(\mathsf{DOMAIN}\_\mathsf{VC}, \mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}, \mathsf{shares}\_\mathsf{hash}, \mathsf{proposal}\_\mathsf{id}, \mathsf{vote}\_\mathsf{decision}\bigr)$$ + +where: + +- $\mathsf{DOMAIN}\_\mathsf{VC} = 1$ — domain tag. +- $\mathsf{shares}\_\mathsf{hash} \in \{ 0 .. q_{\mathbb{P}}-1 \}$ — a hash + over blinded commitments to all $N_s$ encrypted shares (see + [Shares Hash]). +- $\mathsf{proposal}\_\mathsf{id} \in \{1 \ldots 15\}$ — which proposal this + vote targets. +- $\mathsf{vote}\_\mathsf{decision} \in \{ 0 .. q_{\mathbb{P}}-1 \}$ — the + voter's choice (0-indexed into the proposal's declared options). + +A VC MUST be created during voting (Phase 2) and opened during share reveal (Phase 4/5). + +The VC hash MUST be posted on-chain as a public input of +the Vote Proof and inserted into the VCT. + +Its preimage fields +($\mathsf{shares}\_\mathsf{hash}$ and $\mathsf{vote}\_\mathsf{decision}$) +MUST be private witnesses in that proof. + +During share reveal, the Vote +Reveal Proof MUST prove membership in the VCT without exposing which VC +is being opened. + +### Vote Share + +A vote share is one of $N_s = 16$ encrypted portions of a voter's ballot +count within a VC. The value 16 is chosen as a sufficiently high number +to ensure amount privacy through share decomposition. This ZIP +constrains only that the shares sum to $\mathsf{num}\_\mathsf{ballots}$ +and that each share is in $[0, 2^{30})$; the strategy for how the +client decomposes the ballot count across shares (e.g., uniform random +vs. powers-of-two denominations) affects temporal mixing effectiveness +and is specified in [^submission-server]. + +For share index $i \in \{0 \ldots N_s - 1\}$: + +- $\mathsf{v}\_\mathsf{i} \in \{0 \ldots 2^{30} - 1\}$ — plaintext share + amount in ballots (private, never revealed). +- $r_i$ — El Gamal encryption randomness (Pallas scalar, private). +- $\mathsf{blind}\_\mathsf{i} \in \{ 0 .. q_{\mathbb{P}}-1 \}$ — per-share + blind factor for the blinded share commitment (independent of $r_i$). +- $\mathsf{enc}\_{\mathsf{share}\_\mathsf{i}} = \mathsf{Enc}(\mathsf{v}\_\mathsf{i}, r_i) = (C_{1,i}, C_{2,i})$ + — El Gamal ciphertext. + +**Blinded share commitment:** + +$$\mathsf{share}\_{\mathsf{comm}\_\mathsf{i}} = \mathsf{Poseidon}(\mathsf{blind}\_\mathsf{i}, C_{1,i,x}, C_{2,i,x}, C_{1,i,y}, C_{2,i,y})$$ + +where $C_{1,i,x}$, $C_{2,i,x}$ denote the $x$-coordinates and +$C_{1,i,y}$, $C_{2,i,y}$ denote the $y$-coordinates of the ciphertext +points. See [Why Share Commitments Bind Full Curve Points]. + +### Shares Hash + +The shares hash aggregates all $N_s$ blinded share commitments: + +$$\mathsf{shares}\_\mathsf{hash} = \mathsf{Poseidon}(\mathsf{share}\_{\mathsf{comm}\_\mathsf{0}}, \mathsf{share}\_{\mathsf{comm}\_\mathsf{1}}, \ldots, \mathsf{share}\_{\mathsf{comm}_{N_s - 1}})$$ + +This is an $N_s$-input Poseidon sponge hash. + +### Share Nullifier + +When a share is revealed, its nullifier is: + +$$\mathsf{share}\_\mathsf{nullifier} = \mathsf{Poseidon}\bigl(\mathsf{tag}_{\mathsf{share}}, \mathsf{vc}, \mathsf{share}\_\mathsf{index}, \mathsf{blind}\bigr)$$ + +where $\mathsf{tag}_{\mathsf{share}}$ is the field-element encoding of +`"share spend"`, $\mathsf{vc}$ is the vote commitment (private), and +$\mathsf{blind}$ is the blind factor for the revealed share. + +The share +nullifier MUST be posted on-chain as a public input of the Vote Reveal +Proof and added to the share nullifier set to prevent double-counting. + +### Vote Commitment Tree + +The vote chain MUST maintain a separate VCT per voting round. Each +round's VCT MUST be an incremental Merkle tree [^protocol-merkletree] +of depth $\mathsf{MerkleDepth}^{\mathsf{vct}} = 24$ that stores both +VANs and VCs as leaves. Leaves, roots, and tree state from one round +MUST NOT carry over into another. The tree MUST use the same +append-only data structure as the Orchard note commitment tree, but +with Poseidon over the Pallas scalar field for internal node hashing +instead of Sinsemilla (see [Poseidon Instantiation]). + +Domain separation between VANs and VCs is achieved structurally: the +first Poseidon input is $\mathsf{DOMAIN}\_\mathsf{VAN} = 0$ for VANs and +$\mathsf{DOMAIN}\_\mathsf{VC} = 1$ for VCs, making it impossible for a valid VAN +preimage to produce the same hash as a valid VC preimage. + +Leaves MUST be inserted in transaction order: a delegation transaction +inserts one VAN; a vote transaction inserts both a new VAN and a VC. + +### Nullifier Sets + +The vote chain MUST maintain three disjoint nullifier sets: + +1. **Governance nullifiers**: prevent double-delegation of mainchain + Orchard notes within a voting round. +2. **VAN nullifiers**: prevent double-spending of voting authority. +3. **Share nullifiers**: prevent double-counting of revealed shares. + +Each set SHOULD BE append-only within a voting round. The vote chain MUST reject any transaction that publishes a nullifier already present in the corresponding set. + + +The protocol defines three tags: + +| Tag constant | String | Length | +|---|---|---| +| $\mathsf{tag}\_{\mathsf{gov}}$ | `"governance authorization"` | 24 bytes | +| $\mathsf{tag}\_{\mathsf{van}}$ | `"vote authority spend"` | 20 bytes | +| $\mathsf{tag}\_{\mathsf{share}}$ | `"share spend"` | 11 bytes | + + +## Governance Hotkey + +The governance hotkey is a separate Orchard key hierarchy generated on +a general-purpose device (e.g., a mobile phone). It is distinct from +the holder's Orchard spending key, which may reside on a hardware +wallet. The hotkey provides the key material for all voting operations +after delegation. + +A governance hotkey consists of: + +- A fresh spend-authorizing key $\mathsf{vsk}$, from which + $\mathsf{vsk.ak} = [\mathsf{vsk}]\, G$ is derived. +- A nullifier deriving key $\mathsf{vsk.nk}$, derived from the hotkey's + spending key via the standard Orchard key hierarchy. +- A CommitIvk trapdoor $\mathsf{rivk}\_\mathsf{v}$, derived from the + hotkey's spending key. +- A diversified address at a chosen diversifier index, consisting of + the diversified base point and the diversified transmission key point, + where the transmission key satisfies + $[\mathsf{ivk}\_\mathsf{v}]\, \mathsf{g}\_{\mathsf{d}}$ + with $\mathsf{ivk}\_\mathsf{v} = \mathsf{CommitIvk}\_{\mathsf{rivk}\_\mathsf{v}}\!\bigl(\mathsf{Extract}\_{\mathbb{P}}(\mathsf{vsk.ak}),\; \mathsf{vsk.nk}\bigr)$. + The VAN commitment (see [Vote Authority Note (VAN)]) stores the + $x$-coordinates $\mathsf{vpk}\_{\mathsf{g}\_\mathsf{d}} = \mathsf{Extract}\_{\mathbb{P}}(\mathsf{g}\_\mathsf{d})$ + and $\mathsf{vpk}\_{\mathsf{pk}\_\mathsf{d}} = \mathsf{Extract}\_{\mathbb{P}}(\mathsf{pk}\_\mathsf{d})$; + the full curve points are used in the diversified address integrity + check (see [Vote Proof]). + +The Delegation Proof does not constrain the hotkey address to match the +holder's key; the output address is bound to the delegation +transitively through the VAN commitment and the rho binding (see +[Delegation Proof]), which the holder's hardware wallet authenticates +via the spend authorization signature. + +The hotkey MUST be derived deterministically from a seed +$\mathsf{seed}\_\mathsf{v}$. The wallet MUST generate +$\mathsf{seed}\_\mathsf{v}$ by creating a fresh BIP 39 +mnemonic [^bip39], converting it to a 64-byte BIP 39 seed, and +storing the mnemonic in local secure storage (e.g., the platform +keychain). The mnemonic MUST be generated independently of the +holder's wallet mnemonic and MUST NOT be exported or backed up; +it is needed only for the duration of the voting round. + +The wallet computes + +$$\mathsf{sk}\_\mathsf{v} = \mathsf{Blake2b}\text{-}512(\texttt{"ZcashVotingHotKy"},\; \mathsf{seed}\_\mathsf{v})$$ + +interpreted as a Pallas scalar via $\mathsf{FromUniformBytes}$, and +then derives $\mathsf{vsk.ak}$, $\mathsf{vsk.nk}$, and +$\mathsf{rivk}\_\mathsf{v}$ from $\mathsf{sk}\_\mathsf{v}$ following +§ 4.2.3 'Orchard Key Components' [^protocol-orchardkeycomponents]. +See [Why Deterministic Hotkey Derivation]. + +### Per-Vote Secret Derivation + +The El Gamal encryption randomness $r_i$, per-share blind factors +$\mathsf{blind}\_\mathsf{i}$, and spend authorization randomizer +$\alpha_v$ used in the Vote Proof MUST be derived deterministically +from the hotkey seed. This ensures that all per-vote secrets can be +reconstructed from the BIP 39 mnemonic if the app is terminated +between delegation and voting. + +For a vote on proposal $\mathsf{proposal}\_\mathsf{id}$ consuming +VAN commitment $\mathsf{van}\_\mathsf{old}$ in voting round +$\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}$, the wallet MUST +derive a 64-byte per-vote root: + +$$\mathsf{vote}\_\mathsf{root} = \mathsf{Blake2b}\text{-}512\bigl(\texttt{"ZcashVoteSecret"},\; \mathsf{sk}\_\mathsf{v} \| \mathsf{voting}\_{\mathsf{round}\_\mathsf{id}} \| \mathsf{proposal}\_\mathsf{id} \| \mathsf{van}\_\mathsf{old}\bigr)$$ + +where $\|$ denotes concatenation of canonical little-endian encodings +(32 bytes for field elements, 1 byte for +$\mathsf{proposal}\_\mathsf{id}$). + +From $\mathsf{vote}\_\mathsf{root}$, the wallet MUST derive each +per-share secret: + +$$r_i = \mathsf{FromUniformBytes}\!\bigl(\mathsf{Blake2b}\text{-}512(\texttt{"ZcashVoteElGam"},\; \mathsf{vote}\_\mathsf{root} \| i)\bigr)$$ + +$$\mathsf{blind}\_\mathsf{i} = \mathsf{FromUniformBytes}\!\bigl(\mathsf{Blake2b}\text{-}512(\texttt{"ZcashVoteBlind"},\; \mathsf{vote}\_\mathsf{root} \| i)\bigr)$$ + +where $i$ is the share index encoded as a single byte. The spend +authorization randomizer is: + +$$\alpha_v = \mathsf{FromUniformBytes}\!\bigl(\mathsf{Blake2b}\text{-}512(\texttt{"ZcashVoteAlpha"},\; \mathsf{vote}\_\mathsf{root})\bigr)$$ + +All Blake2b invocations above use the first argument as the +personalization parameter (truncated or padded to 16 bytes per the +Blake2b spec) and the second argument as the input message. +$\mathsf{FromUniformBytes}$ interprets a 64-byte input as a Pallas +scalar. [^protocol-pallasandvesta] + +See [Why Deterministic Hotkey Derivation]. + + +## Ballot Scaling + +Orchard note values are denominated in zatoshi. The Delegation Proof +MUST convert zatoshi to ballots: + +$$\mathsf{num}\_\mathsf{ballots} = \left\lfloor \frac{\sum v_i}{12{,}500{,}000} \right\rfloor$$ + +where $v_i$ are the values of the delegated Orchard notes. One ballot +equals 0.125 ZEC. + +The prover MUST witness $\mathsf{num}\_\mathsf{ballots}$ and a +remainder $r$. The Delegation Proof circuit MUST enforce: + +1. $\mathsf{num}\_\mathsf{ballots} \times 12{,}500{,}000 + r = \sum v_i$ +2. $0 \leq r < 2^{24}$ (see [Why a 24-bit Remainder Range]) +3. $\mathsf{num}\_\mathsf{ballots} \geq 1$ +4. $\mathsf{num}\_\mathsf{ballots} \leq 2^{30}$ + +The 30-bit upper bound accommodates up to $\approx 134$ million ZEC, +well above the 21 million ZEC supply cap. The minimum of 1 ballot +ensures that holdings below 0.125 ZEC MUST NOT produce voting +authority. + + +## Delegation Phase + +### Delegation Proof + +The Delegation Proof establishes that a holder owns unspent Orchard +notes at a pool snapshot and converts the proven balance into a VAN on +the vote chain. + +The Delegation Proof circuit MUST enforce the same per-note ownership +checks (note commitment integrity, Merkle path validity, nullifier +derivation, diversified address integrity, and nullifier +non-membership) as the Batched Claim circuit defined +in [^balance-proof]. This section specifies only the conditions that +extend beyond the Balance Proof. + +#### Public Inputs + +Given a primary input: + +- $\mathsf{signed}\_{\mathsf{note}\_\mathsf{nullifier}} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ — + nullifier of the dummy signed note (for spend authorization binding). +- $\mathsf{rk} ⦂ \mathsf{SpendAuthSig}^{\mathsf{Orchard}}\mathsf{.Public}$ — + randomized spend authorization verification key. +- $\mathsf{rt}^{\mathsf{cm}} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ — Orchard note + commitment tree root at the snapshot height. +- $\mathsf{rt}^{\mathsf{excl}} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ — nullifier + non-membership tree root at the snapshot height. +- $\mathsf{van} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ — the initial VAN + commitment. +- $\mathsf{gov}\_{\mathsf{null}\_\mathsf{1}}, \ldots, \mathsf{gov}\_{\mathsf{null}\_\mathsf{5}} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ — + governance nullifiers for each note slot. +- $\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ +- $\mathsf{cmx}\_\mathsf{new} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ — output note + commitment to the governance hotkey address (PCZT scaffolding; not + inserted into any on-chain tree). + +#### Auxiliary Inputs + +The prover knows, in addition to the per-note witnesses defined +in [^balance-proof]: + +- $\mathsf{vpk} = (\mathsf{vpk}\_{\mathsf{g}\_\mathsf{d}}, \mathsf{vpk}\_{\mathsf{pk}\_\mathsf{d}})$ — + governance hotkey diversified address. +- $\mathsf{gov}\_{\mathsf{comm}\_\mathsf{rand}}$ — VAN commitment randomness. +- Signed note data: $\mathsf{g}\_\mathsf{d}^{\mathsf{signed}}, \mathsf{pk}\_\mathsf{d}^{\mathsf{signed}}, + \text{ρ}^{\mathsf{signed}}, \text{ψ}^{\mathsf{signed}}, + \mathsf{rcm}^{\mathsf{signed}}, \mathsf{cm}^{\mathsf{signed}}$ (value = 0). +- Output note data (PCZT scaffolding): $\mathsf{g}\_\mathsf{d}^{\mathsf{new}}, \mathsf{pk}\_\mathsf{d}^{\mathsf{new}}, + \mathsf{v}^{\mathsf{new}}, \text{ρ}^{\mathsf{new}}, + \text{ψ}^{\mathsf{new}}, \mathsf{rcm}^{\mathsf{new}}$ (address = hotkey). + $\mathsf{v}^{\mathsf{new}}$ is not constrained by the circuit. + +#### Conditions + +**Per-note conditions (5 note slots, with padding).** For each note +$i \in \{1 \ldots 5\}$, the circuit MUST enforce the following +conditions from the Batched Claim circuit [^balance-proof]: + +- Note commitment integrity. +- Merkle path validity in $\mathsf{rt}^{\mathsf{cm}}$ (skipped for padded + notes). +- Diversified address integrity (same $\mathsf{ivk}$ MUST own all notes). +- Standard nullifier derivation (kept private). +- Nullifier non-membership in $\mathsf{rt}^{\mathsf{excl}}$ (skipped for padded + notes). +- Padded notes MUST have value 0. + +**Governance nullifier derivation.** For each real note $i$, the +circuit MUST enforce: + +$$\mathsf{gov}\_{\mathsf{null}\_\mathsf{i}} = \mathsf{Poseidon}\bigl(\mathsf{nk}, \mathsf{tag}_{\mathsf{gov}}, \mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}, \mathsf{nf}^{\mathsf{old}}_i\bigr)$$ + +where $\mathsf{tag}_{\mathsf{gov}}$ is the field-element encoding of +"governance authorization" and $\mathsf{nf}^{\mathsf{old}}_i$ is the note's +standard nullifier (computed in-circuit but never revealed). This is an +instantiation of the alternate nullifier derivation defined +in [^balance-proof], with $\mathsf{tag} = \mathsf{tag}_{\mathsf{gov}}$ and +$\mathsf{dom} = \mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}$. + +**Signed note integrity.** The signed note MUST be a dummy note with +value 0. The circuit MUST enforce that +$\mathsf{cm}^{\mathsf{signed}}$ is correctly constructed and that +$\mathsf{signed}\_{\mathsf{note}\_\mathsf{nullifier}}$ is correctly derived from it. + +**Rho binding.** The circuit MUST enforce that +$\text{ρ}^{\mathsf{signed}}$ is deterministically bound to the +delegation context: + +$$\text{ρ}^{\mathsf{signed}} = \mathsf{Poseidon}\bigl(\mathsf{cmx}\_\mathsf{1}, \mathsf{cmx}\_\mathsf{2}, \mathsf{cmx}\_\mathsf{3}, \mathsf{cmx}\_\mathsf{4}, \mathsf{cmx}\_\mathsf{5}, \mathsf{van}, \mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}\bigr)$$ + +This makes the spend authorization signature non-replayable and scoped +to the exact delegation context. + +**Spend authority.** The circuit MUST enforce that $\mathsf{rk} = \mathsf{SpendAuthSig}^{\mathsf{Orchard}}\mathsf{.RandomizePublic}(\alpha, \mathsf{ak}^{\mathbb{P}})$. + +**Diversified address integrity for signed note.** The circuit MUST +enforce that the signed note's address belongs to +$(\mathsf{ak}, \mathsf{nk})$. + +**Output note commitment.** The circuit MUST enforce that +$\mathsf{cmx}\_\mathsf{new}$ is a correctly constructed note commitment +to an output note addressed to the governance hotkey. The output note +exists solely to satisfy the PCZT signing flow: a standard Orchard +Action requires exactly one spend and one output, so the governance +PCZT includes a minimal output to the hotkey address. + +The vote chain SHOULD NOT insert $\mathsf{cmx}\_\mathsf{new}$ into any commitment tree +or use it beyond proof verification; it serves only to make the +hardware wallet's signed sighash commit to a complete Action structure. + +See [^balance-proof] for the PCZT construction details and +[Why a Dummy Signed Note] for the design rationale. + +**VAN integrity.** The circuit MUST enforce that the public VAN +commitment matches the claimed governance hotkey, ballot count, round, +and full proposal authority (using the two-layer construction defined +in [Vote Authority Note (VAN)]): + +$$\mathsf{van}\_\mathsf{core} = \mathsf{Poseidon}\bigl(\mathsf{DOMAIN}\_\mathsf{VAN}, \mathsf{vpk}\_{\mathsf{g}\_\mathsf{d}}, \mathsf{vpk}\_{\mathsf{pk}\_\mathsf{d}}, \mathsf{num}\_\mathsf{ballots}, \mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}, \mathsf{MAX}\_{\mathsf{PROPOSAL}\_\mathsf{AUTHORITY}}\bigr)$$ + +$$\mathsf{van} = \mathsf{Poseidon}\bigl(\mathsf{van}\_\mathsf{core}, \mathsf{gov}\_{\mathsf{comm}\_\mathsf{rand}}\bigr)$$ + +where $\mathsf{MAX}\_{\mathsf{PROPOSAL}\_\mathsf{AUTHORITY}} = 2^{16} - 1$. + +**Ballot scaling.** The circuit MUST enforce that +$\mathsf{num}\_\mathsf{ballots} = \lfloor \sum v_i / 12{,}500{,}000 \rfloor$ +with $\mathsf{num}\_\mathsf{ballots} \geq 1$, as defined in [Ballot Scaling]. + +#### Out-of-Circuit Verification + +A verifier that receives a Delegation Proof $\pi$ together with a spend +authorization signature $\sigma$ MUST perform the following checks: + +1. Verify $\pi$ against the public inputs. +2. Verify that $\mathsf{sighash}\_\mathsf{del}$ is exactly 32 bytes. +3. Verify $\sigma$ as a valid $\mathsf{SpendAuthSig}^{\mathsf{Orchard}}$ + signature on $\mathsf{sighash}\_\mathsf{del}$, under $\mathsf{rk}$. +4. Verify that $\mathsf{rt}^{\mathsf{cm}}$ and $\mathsf{rt}^{\mathsf{excl}}$ correspond + to the published pool snapshot for $\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}$. +5. Verify that no $\mathsf{gov}\_{\mathsf{null}\_\mathsf{i}}$ appears in the governance + nullifier set. If any does, reject as a double-delegation. +6. Verify that $\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}$ matches an active round. +7. Insert $\mathsf{van}$ into the VCT. +8. Add all $\mathsf{gov}\_{\mathsf{null}\_\mathsf{i}}$ to the governance nullifier set. + +### Delegation Sighash + +The delegation sighash $\mathsf{sighash}\_\mathsf{del}$ is a +client-provided 32-byte value included in the delegation message. For +hardware wallet flows, the client computes it as the ZIP 244 [^zip-244] +shielded transaction sighash of a governance PCZT [^pczt]; the +construction of this PCZT is specified +in [^balance-proof]. For software wallets, the client signs the +sighash directly without PCZT construction. + +The vote chain verifies the +$\mathsf{SpendAuthSig}^{\mathsf{Orchard}}$ against this +client-provided sighash without recomputing it. The sighash does not +need to be independently reconstructible by the vote chain because the +Delegation Proof provides the governance data binding: the ZKP proves +that $\mathsf{rk}$ is a valid rerandomization of the holder's spend +authorization key, that the holder owns the claimed notes, and that the +VAN commitment is correctly constructed. An attacker who substitutes a +different sighash cannot produce a valid signature under +$\mathsf{rk}$ without knowledge of the holder's spending key. + +### Delegation Message + +A delegation transaction submitted to the vote chain MUST contain: + +| Field | Type | Description | +|---|---|---| +| $\pi\_\mathsf{del}$ | Proof | The Delegation Proof | +| $\sigma\_\mathsf{del}$ | Signature | SpendAuthSig under $\mathsf{rk}$ | +| $\mathsf{sighash}\_\mathsf{del}$ | 32 bytes | Client-computed sighash (see [Delegation Sighash]) | +| $\mathsf{signed}\_{\mathsf{note}\_\mathsf{nullifier}}$ | Pallas scalar | Dummy note nullifier | +| $\mathsf{rk}$ | Pallas point | Randomized verification key | +| $\mathsf{rt}^{\mathsf{cm}}$ | Pallas scalar | Note commitment tree root | +| $\mathsf{rt}^{\mathsf{excl}}$ | Pallas scalar | Non-membership tree root | +| $\mathsf{van}$ | Pallas scalar | VAN commitment | +| $\mathsf{gov}\_{\mathsf{null}\_1} \ldots \mathsf{gov}\_{\mathsf{null}\_5}$ | Pallas scalar each | Governance nullifiers | +| $\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}$ | Pallas scalar | Round identifier | +| $\mathsf{cmx}\_\mathsf{new}$ | Pallas scalar | Output note commitment | + + +## Vote Phase + +### Vote Proof + +The Vote Proof demonstrates that a holder of a valid VAN is casting a +vote: consuming the old VAN, producing a new VAN with decremented +proposal authority, and constructing a Vote Commitment that binds $N_s$ +El Gamal-encrypted shares to the chosen proposal and decision. The +Vote Proof circuit MUST enforce all conditions specified below. + +#### Public Inputs + +Given a primary input: + +- $\mathsf{van}\_\mathsf{nullifier} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ — + nullifier of the old VAN (prevents double-voting). +- $\mathsf{r}\_\mathsf{vpk} ⦂ \mathsf{SpendAuthSig}^{\mathsf{Orchard}}\mathsf{.Public}$ — + randomized voting public key. +- $\mathsf{van}\_{\mathsf{new}} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ — the new VAN + commitment with decremented proposal authority. +- $\mathsf{vc} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ — the vote commitment. +- $\mathsf{rt}^{\mathsf{vct}} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ — root of the + Vote Commitment Tree. +- $\mathsf{anchor}\_\mathsf{height} ⦂ \mathbb{N}$ — VCT snapshot height. +- $\mathsf{proposal}\_\mathsf{id} ⦂ \{1 \ldots 15\}$ — which proposal. +- $\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ +- $\mathsf{ea}\_\mathsf{pk} ⦂ \mathbb{P}^*$ — election authority public key + (x and y coordinates). + +#### Auxiliary Inputs + +The prover knows: + +- $\mathsf{vote}\_\mathsf{decision} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ — the voter's choice. +- $\mathsf{vsk.ak} ⦂ \mathbb{P}^*$ — voting spend authorization + validating key. +- $\mathsf{vsk.nk} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ — nullifier + deriving key from the governance hotkey's full viewing key. +- $\mathsf{rivk}\_\mathsf{v} ⦂ \mathsf{Commit}^{\mathsf{ivk}}\mathsf{.Trapdoor}$ — + CommitIvk randomness for the voting key. +- $\alpha_v$ — spend authorization randomizer for the voting hotkey. +- $\mathsf{vpk}\_{\mathsf{g}\_\mathsf{d}} ⦂ \mathbb{P}^*$ — diversified base point from the + VAN (full point; $\mathsf{Extract}\_{\mathbb{P}}$ applied for VAN integrity hash). +- $\mathsf{vpk}\_{\mathsf{pk}\_\mathsf{d}} ⦂ \mathbb{P}^*$ — diversified transmission + key point from the VAN (full point; $\mathsf{Extract}\_{\mathbb{P}}$ applied for + VAN integrity hash). +- $\mathsf{num}\_\mathsf{ballots}$ — total voting weight in ballots. +- $\mathsf{proposal}\_{\mathsf{authority}\_\mathsf{old}}$, + $\mathsf{proposal}\_{\mathsf{authority}\_\mathsf{new}}$ — old and new bitmasks. +- $\mathsf{gov}\_{\mathsf{comm}\_\mathsf{rand}}$ — VAN commitment randomness (shared + between old and new VAN). +- $\mathsf{path}^{\mathsf{vct}}, \mathsf{pos}^{\mathsf{vct}}$ — Merkle proof for the + old VAN in the VCT. +- $\mathsf{van}_{\mathsf{old}}$ — old VAN commitment. +- $\mathsf{v}\_\mathsf{0}, \ldots, \mathsf{v}_{N_s - 1}$ — plaintext share values. +- $r_0, \ldots, r_{N_s - 1}$ — El Gamal encryption randomness per share. +- $\mathsf{blind}\_\mathsf{0}, \ldots, \mathsf{blind}_{N_s - 1}$ — per-share blind + factors. + +#### Conditions + +##### VAN Ownership and Spending + +**Condition 1: Merkle tree membership.** The circuit MUST enforce that +the old VAN exists in the VCT: +$(\mathsf{path}^{\mathsf{vct}}, \mathsf{pos}^{\mathsf{vct}})$ MUST be +a valid Merkle path of depth $\mathsf{MerkleDepth}^{\mathsf{vct}}$ +from $\mathsf{van}_{\mathsf{old}}$ to the anchor +$\mathsf{rt}^{\mathsf{vct}}$, using Poseidon for internal node hashing. + +**Condition 2: Old VAN integrity.** The circuit MUST enforce that +the old VAN commitment matches the claimed fields (using the two-layer +construction defined in [Vote Authority Note (VAN)]). The +$\mathsf{vpk}$ values in the Poseidon input are x-coordinates, i.e., +$\mathsf{Extract}\_{\mathbb{P}}$ of the corresponding full points: + +$$\mathsf{van}\_{\mathsf{core}\_\mathsf{old}} = \mathsf{Poseidon}\bigl(\mathsf{DOMAIN}\_\mathsf{VAN}, \mathsf{vpk}\_{\mathsf{g}\_\mathsf{d}}, \mathsf{vpk}\_{\mathsf{pk}\_\mathsf{d}}, \mathsf{num}\_\mathsf{ballots}, \mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}, \mathsf{proposal}\_{\mathsf{authority}\_\mathsf{old}}\bigr)$$ + +$$\mathsf{van}\_\mathsf{old} = \mathsf{Poseidon}\bigl(\mathsf{van}\_{\mathsf{core}\_\mathsf{old}}, \mathsf{gov}\_{\mathsf{comm}\_\mathsf{rand}}\bigr)$$ + +**Condition 3: Diversified address integrity.** The circuit MUST +enforce that the VAN's address belongs to the voting key: + +$$\mathsf{vpk}\_{\mathsf{pk}\_\mathsf{d}} = [\mathsf{ivk}\_\mathsf{v}]\, \mathsf{vpk}\_{\mathsf{g}\_\mathsf{d}}$$ + +where $\mathsf{ivk}\_\mathsf{v} = \mathsf{CommitIvk}_{\mathsf{rivk}\_\mathsf{v}}\!\bigl(\mathsf{Extract}_{\mathbb{P}}(\mathsf{vsk.ak}),\; \mathsf{vsk.nk}\bigr)$ [^protocol-concretecommitivk]. + +**Condition 4: Spend authority.** The circuit MUST enforce that the +randomized voting public key is a valid rerandomization: + +$$\mathsf{r}\_\mathsf{vpk} = \mathsf{vsk.ak} + [\alpha_v]\, G$$ + +**Condition 5: VAN nullifier.** The circuit MUST enforce that the +public $\mathsf{van}\_\mathsf{nullifier}$ is correctly derived: + +$$\mathsf{van}\_\mathsf{nullifier} = \mathsf{Poseidon}\bigl(\mathsf{vsk.nk}, \mathsf{tag}_{\mathsf{van}}, \mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}, \mathsf{van}_{\mathsf{old}}\bigr)$$ + +where $\mathsf{tag}_{\mathsf{van}}$ is the field-element encoding of +`"vote authority spend"`. + +See [Why VAN Nullifier Domain Separation]. + +##### New VAN Construction + +**Condition 6: Proposal authority decrement.** The circuit MUST +enforce that bit $\mathsf{proposal}\_\mathsf{id}$ is cleared in the +authority bitmask: + +- $\mathsf{proposal}\_{\mathsf{authority}\_\mathsf{old}}$ MUST be decomposed into + 16 boolean wires $b_0, \ldots, b_{15}$ that recompose to the original + value. +- The bit at position $\mathsf{proposal}\_\mathsf{id}$ MUST be 1 (the voter + has authority for this proposal). +- $\mathsf{proposal}\_{\mathsf{authority}\_\mathsf{new}}$ MUST be the recomposition + with bit $\mathsf{proposal}\_\mathsf{id}$ cleared; all other bits MUST be + unchanged. + +**Condition 7: New VAN integrity.** The circuit MUST enforce that the +new VAN is correctly constructed (using the two-layer construction +defined in [Vote Authority Note (VAN)]): + +$$\mathsf{van}\_{\mathsf{core}\_\mathsf{new}} = \mathsf{Poseidon}\bigl(\mathsf{DOMAIN}\_\mathsf{VAN}, \mathsf{vpk}\_{\mathsf{g}\_\mathsf{d}}, \mathsf{vpk}\_{\mathsf{pk}\_\mathsf{d}}, \mathsf{num}\_\mathsf{ballots}, \mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}, \mathsf{proposal}\_{\mathsf{authority}\_\mathsf{new}}\bigr)$$ + +$$\mathsf{van}\_\mathsf{new} = \mathsf{Poseidon}\bigl(\mathsf{van}\_{\mathsf{core}\_\mathsf{new}}, \mathsf{gov}\_{\mathsf{comm}\_\mathsf{rand}}\bigr)$$ + +The new VAN MUST reuse the old VAN's diversified address and commitment +randomness; only $\mathsf{proposal}\_\mathsf{authority}$ changes. + +##### Vote Commitment Construction + +**Condition 8: Shares sum correctness.** The circuit MUST enforce: + +$$\sum_{i=0}^{N_s - 1} \mathsf{v}\_\mathsf{i} = \mathsf{num}\_\mathsf{ballots}$$ + +**Condition 9: Shares range check.** The circuit MUST enforce that +each share is bounded: + +$$0 \leq \mathsf{v}\_\mathsf{i} < 2^{30} \quad \text{for each } i \in \{0 \ldots N_s - 1\}$$ + +This bound is critical for two reasons: (1) it ensures the base-field +share sum and the scalar-field El Gamal encoding agree (no modular +reduction in either field), and (2) it keeps the aggregate discrete log +small enough for efficient recovery at tally time (see [^ea-ceremony]). + +**Condition 10: Shares hash integrity.** The circuit MUST enforce that +the blinded share commitments and their aggregate hash are correctly +computed: + +$$\mathsf{share}\_{\mathsf{comm}\_\mathsf{i}} = \mathsf{Poseidon}\bigl(\mathsf{blind}\_\mathsf{i}, C_{1,i,x}, C_{2,i,x}, C_{1,i,y}, C_{2,i,y}\bigr) \quad \text{for each } i$$ + +$$\mathsf{shares}\_\mathsf{hash} = \mathsf{Poseidon}\bigl(\mathsf{share}\_{\mathsf{comm}\_\mathsf{0}}, \ldots, \mathsf{share}\_{\mathsf{comm}_{N_s - 1}}\bigr)$$ + +**Condition 11: El Gamal encryption integrity.** The circuit MUST +enforce that each ciphertext is a valid encryption of its share under +$\mathsf{ea}\_\mathsf{pk}$: + +$$C_{1,i} = [r_i]\, G$$ +$$C_{2,i} = [\mathsf{v}\_\mathsf{i}]\, G + [r_i]\, \mathsf{ea}\_\mathsf{pk}$$ + +The circuit MUST constrain equality on both the $x$- and +$y$-coordinates of the computed and witnessed ciphertext points. The +$y$-coordinates are needed because they appear in the share commitment +(condition 10); the ECC gadget already produces full curve points, so +no additional decomposition cost is incurred. +See [Why Share Commitments Bind Full Curve Points]. + +**Condition 12: Vote commitment integrity.** The circuit MUST enforce +that the public vote commitment matches the private vote details: + +$$\mathsf{vc} = \mathsf{Poseidon}\bigl(\mathsf{DOMAIN}\_\mathsf{VC}, \mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}, \mathsf{shares}\_\mathsf{hash}, \mathsf{proposal}\_\mathsf{id}, \mathsf{vote}\_\mathsf{decision}\bigr)$$ + +#### Out-of-Circuit Verification + +A verifier that receives a Vote Proof $\pi$ together with a vote +spend authorization signature $\sigma$ MUST perform the following +checks: + +1. Verify $\pi$ against the public inputs. +2. Compute the vote sighash from the message fields + (see [Vote Sighash]). +3. Verify $\sigma$ as a valid $\mathsf{SpendAuthSig}^{\mathsf{Orchard}}$ + signature on the vote sighash, under $\mathsf{r}\_\mathsf{vpk}$. +4. Verify that $\mathsf{van}\_\mathsf{nullifier}$ does not appear in the VAN + nullifier set. If it does, reject as double-voting. +5. Verify that $\mathsf{anchor}\_\mathsf{height}$ refers to a VCT state + within the current voting round and that $\mathsf{rt}^{\mathsf{vct}}$ + matches the VCT root at that height. +6. Verify that $\mathsf{proposal}\_\mathsf{id}$ is valid for the current round + and within the voting window. +7. Verify that $\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}$ matches an active round. +8. Verify that $\mathsf{ea}\_\mathsf{pk}$ matches the published election + authority public key for this round. +9. Insert $\mathsf{van}_{\mathsf{new}}$ and $\mathsf{vc}$ into the VCT. +10. Add $\mathsf{van}\_\mathsf{nullifier}$ to the VAN nullifier set. + +Note: $\mathsf{vote}\_\mathsf{decision}$ is a private witness in the +Vote Proof and MUST NOT be validated out-of-circuit at this stage. + +It is validated when the share is revealed: +the Vote Reveal Proof makes +$\mathsf{vote}\_\mathsf{decision}$ a public input, and the vote chain +checks its validity at that point (see [Vote Reveal Proof] +out-of-circuit step 6). + +### Vote Sighash + +The vote sighash is computed by the vote chain from the vote message +fields. Unlike the delegation sighash, it is not client-provided; +the governance hotkey is software-controlled and signs the same +chain-computable digest. + +The vote sighash is $\mathsf{BLAKE2b\text{-}256}(\mathsf{vote}\_\mathsf{payload})$ +using an unkeyed BLAKE2b-256 hash (no personalization parameter). +$\mathsf{vote}\_\mathsf{payload}$ is the concatenation of a domain +prefix followed by the message fields, each zero-padded to 32 bytes: + +| Component | Encoding | Size | +|---|---|---| +| `"SVOTE_CAST_VOTE_SIGHASH_V0"` | raw ASCII bytes | 26 | +| $\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}$ | field element, zero-padded to 32 | 32 | +| $\mathsf{r}\_\mathsf{vpk}$ | compressed point, zero-padded to 32 | 32 | +| $\mathsf{van}\_\mathsf{nullifier}$ | field element, zero-padded to 32 | 32 | +| $\mathsf{van}\_{\mathsf{new}}$ | field element, zero-padded to 32 | 32 | +| $\mathsf{vc}$ | field element, zero-padded to 32 | 32 | +| $\mathsf{proposal}\_\mathsf{id}$ | 4-byte LE integer, zero-padded to 32 | 32 | +| $\mathsf{anchor}\_\mathsf{height}$ | 8-byte LE integer, zero-padded to 32 | 32 | + +Total payload: 250 bytes. Pallas field elements and compressed points +use their canonical little-endian +encoding [^protocol-pallasandvesta], right-padded with zero bytes to +fill 32 bytes when shorter. Integer fields are encoded as unsigned +little-endian and right-padded with zero bytes to 32 bytes. + +The voter's client MUST compute the same digest and sign it with +the governance hotkey's spend-authorizing key (rerandomized by +$\alpha_v$) before submitting the vote message. + +### Vote Message + +A vote transaction submitted to the vote chain MUST contain: + +| Field | Type | Description | +|---|---|---| +| $\pi_{\text{vote}}$ | Proof | The Vote Proof | +| $\sigma_{\text{vote}}$ | Signature | SpendAuthSig under $\mathsf{r}_{\mathsf{vpk}}$ | +| $\mathsf{van}\_\mathsf{nullifier}$ | Pallas scalar | Old VAN nullifier | +| $\mathsf{r}_{\mathsf{vpk}}$ | Pallas point | Randomized voting public key | +| $\mathsf{van}_{\mathsf{new}}$ | Pallas scalar | New VAN commitment | +| $\mathsf{vc}$ | Pallas scalar | Vote commitment | +| $\mathsf{rt}^{\mathsf{vct}}$ | Pallas scalar | VCT root | +| $\mathsf{anchor}\_\mathsf{height}$ | integer | VCT anchor height | +| $\mathsf{proposal}\_\mathsf{id}$ | $\{1 \ldots 15\}$ | Proposal identifier | +| $\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}$ | Pallas scalar | Round identifier | +| $\mathsf{ea}_{\mathsf{pk}}$ | Pallas point | EA public key | + + +## Share Reveal Phase + +### Vote Reveal Proof + +The Vote Reveal Proof opens a single encrypted share from a Vote +Commitment, revealing the El Gamal ciphertext for homomorphic +accumulation, without revealing the plaintext amount or which Vote +Commitment the share came from. This proof is constructed by the +submission server, not the voter. The Vote Reveal Proof circuit MUST +enforce all conditions specified below. + +#### Public Inputs + +Given a primary input: + +- $\mathsf{share}\_\mathsf{nullifier} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ — + prevents double-counting. +- $C_{1,x}, C_{1,y}, C_{2,x}, C_{2,y} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ — the + $x$- and $y$-coordinates of the El Gamal ciphertext points $(C_1, C_2)$ + for this share. +- $\mathsf{proposal}\_\mathsf{id} ⦂ \{1 \ldots 15\}$ — which proposal. +- $\mathsf{vote}\_\mathsf{decision} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ — the voter's choice. +- $\mathsf{rt}^{\mathsf{vct}} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ — root of the + Vote Commitment Tree. +- $\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ + +#### Auxiliary Inputs + +The prover (submission server) knows: + +- $\mathsf{vc} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ — the vote commitment + being opened (hidden from the verifier). +- $\mathsf{path}^{\mathsf{vct}}, \mathsf{pos}^{\mathsf{vct}}$ — Merkle proof for the VC + in the VCT. +- $\mathsf{shares}\_\mathsf{hash} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ +- $\mathsf{share}\_\mathsf{index} \in \{0, 1, \ldots, N_s - 1\}$ — which share is being revealed. +- $\mathsf{share}\_{\mathsf{comm}\_0} \ldots \mathsf{share}\_{\mathsf{comm}\_{N_s - 1}}$ — + all $N_s$ blinded share commitments (to recompute + $\mathsf{shares}\_\mathsf{hash}$). +- $\mathsf{blind}$ — the blind factor for the revealed share + (at position $\mathsf{share}\_\mathsf{index}$). + +#### Conditions + +##### Vote Commitment Membership + +**Condition 1: Merkle tree membership.** The circuit MUST enforce that +the VC exists in the VCT: +$(\mathsf{path}^{\mathsf{vct}}, \mathsf{pos}^{\mathsf{vct}})$ MUST be +a valid Merkle path from $\mathsf{vc}$ to $\mathsf{rt}^{\mathsf{vct}}$, +without revealing which leaf. The VC value is a private witness. + +**Condition 2: Vote commitment integrity.** The circuit MUST enforce +that the VC is correctly constructed from its components: + +$$\mathsf{vc} = \mathsf{Poseidon}\bigl(\mathsf{DOMAIN}\_\mathsf{VC}, \mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}, \mathsf{shares}\_\mathsf{hash}, \mathsf{proposal}\_\mathsf{id}, \mathsf{vote}\_\mathsf{decision}\bigr)$$ + +This binds the public $\mathsf{proposal}\_\mathsf{id}$ and +$\mathsf{vote}\_\mathsf{decision}$ to the private VC, ensuring that the +revealed share is attributed to the correct proposal and decision. + +##### Share Opening + +**Condition 3: Shares hash integrity.** The circuit MUST enforce that +$\mathsf{shares}\_\mathsf{hash}$ is recomputed from the witness share +commitments: + +$$\mathsf{shares}\_\mathsf{hash} = \mathsf{Poseidon}\bigl(\mathsf{share}\_{\mathsf{comm}\_\mathsf{0}}, \ldots, \mathsf{share}\_{\mathsf{comm}_{N_s - 1}}\bigr)$$ + +The recomputed $\mathsf{shares}\_\mathsf{hash}$ MUST equal the one inside +the VC (via condition 2). The share commitments are blinded +(see [Why Blinded Share Commitments]), so they do not reveal the +ciphertexts or blind factors of other shares to the prover. + +**Condition 4: Share membership.** The circuit MUST enforce that the +commitment derived from the public ciphertext coordinates +$C_{1,x}, C_{2,x}, C_{1,y}, C_{2,y}$ and the witness blind factor +matches the share commitment at position +$\mathsf{share}\_\mathsf{index}$: + +$$\mathsf{Poseidon}\bigl(\mathsf{blind}_{\mathsf{share}\_\mathsf{index}}, C_{1,x}, C_{2,x}, C_{1,y}, C_{2,y}\bigr) = \mathsf{share}\_\mathsf{comms}[\mathsf{share}\_\mathsf{index}]$$ + +The circuit MUST encode $\mathsf{share}\_\mathsf{index}$ as a one-hot +selector vector over $N_s$ positions. The mux MUST extract the +corresponding $\mathsf{share}\_\mathsf{comm}$ via a dot product and +constrain equality with the commitment derived from the public +ciphertext coordinates and the witness blind factor. Only the blind +factor for the revealed share is needed; the remaining $N_s - 1$ share +commitments used in condition 3 are opaque witnesses that do not expose +their underlying ciphertexts or blind factors. + +##### Nullifier + +**Condition 5: Share nullifier.** The circuit MUST enforce that the +public $\mathsf{share}\_\mathsf{nullifier}$ is correctly derived: + +$$\mathsf{share}\_\mathsf{nullifier} = \mathsf{Poseidon}\bigl(\mathsf{tag}_{\mathsf{share}}, \mathsf{vc}, \mathsf{share}\_\mathsf{index}, \mathsf{blind}\bigr)$$ + +where $\mathsf{tag}_{\mathsf{share}}$ is the field-element encoding of +`"share spend"` and $\mathsf{blind}$ is the blind factor for the +revealed share. The VC and blind are private, making the nullifier +unlinkable to a specific VC without knowledge of these witnesses. + +#### Out-of-Circuit Verification + +A verifier that receives a Vote Reveal Proof $\pi$ MUST perform the +following checks: + +1. Verify $\pi$ against the public inputs. +2. Verify that $\mathsf{share}\_\mathsf{nullifier}$ does not appear in the + share nullifier set. If it does, reject as double-counting. +3. Verify that $\mathsf{rt}^{\mathsf{vct}}$ matches a published VCT root. +4. Verify that $\mathsf{proposal}\_\mathsf{id}$ is valid for the current round. +5. Verify that $\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}$ matches an active round. +6. Verify that $\mathsf{vote}\_\mathsf{decision}$ is a valid option for the + proposal identified by $\mathsf{proposal}\_\mathsf{id}$ in this round. +7. Add $\mathsf{share}\_\mathsf{nullifier}$ to the share nullifier set. +8. Accumulate $\mathsf{enc}\_\mathsf{share}$ into the aggregate ciphertext for + $(\mathsf{proposal}\_\mathsf{id}, \mathsf{vote}\_\mathsf{decision})$: + +$$\mathsf{agg}[\mathsf{proposal}\_\mathsf{id}][\mathsf{vote}\_\mathsf{decision}] \mathrel{+}= \mathsf{enc}\_\mathsf{share}$$ + +where $+$ denotes component-wise Pallas point addition. + +### Share Reveal Message + +A share reveal transaction submitted to the vote chain MUST contain: + +| Field | Type | Description | +|---|---|---| +| $\pi_{\text{reveal}}$ | Proof | The Vote Reveal Proof | +| $\mathsf{share}\_\mathsf{nullifier}$ | Pallas scalar | Share nullifier | +| $\mathsf{enc}\_\mathsf{share}$ | $(C_1, C_2)$ | El Gamal ciphertext (two Pallas points) | +| $\mathsf{proposal}\_\mathsf{id}$ | $\{1 \ldots 15\}$ | Proposal identifier | +| $\mathsf{vote}\_\mathsf{decision}$ | Pallas scalar | Vote decision | +| $\mathsf{rt}^{\mathsf{vct}}$ | Pallas scalar | VCT root | +| $\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}$ | Pallas scalar | Round identifier | + +Note: the Vote Reveal Proof has no spend authorization signature +because it is constructed by the submission server, not the voter. + +### Share Submission Payload + +The voter sends each share to a submission server as an off-chain +payload. For share $i$, the payload MUST contain: + +| Field | Description | +|---|---| +| $\mathsf{vc}$ | The vote commitment | +| VCT position | Position of $\mathsf{vc}$ in the VCT | +| $\mathsf{shares}\_\mathsf{hash}$ | Hash of all blinded share commitments | +| $\mathsf{proposal}\_\mathsf{id}$ | Proposal identifier | +| $\mathsf{vote}\_\mathsf{decision}$ | Vote decision | +| $\mathsf{share}\_\mathsf{index}$ | Which share to reveal (0-indexed) | +| $\mathsf{enc}\_\mathsf{share}$ | El Gamal ciphertext $(C_1, C_2)$ for this share | +| $\mathsf{blind}$ | Blind factor for this share | +| $\mathsf{share}\_{\mathsf{comm}\_0} \ldots \mathsf{share}\_{\mathsf{comm}\_{N_s - 1}}$ | All $N_s$ blinded share commitments | +| $\mathsf{submit\_at}$ | Unix timestamp (seconds) for when the server should submit the share reveal transaction. 0 means immediate (last-moment mode). See [^submission-server] | + +The server receives only the ciphertext and blind factor for the +single share it is responsible for revealing. The remaining $N_s - 1$ +shares are sent to other servers, each of which receives only its own +share's raw data. To recompute $\mathsf{shares}\_\mathsf{hash}$ in +condition 3 of the Vote Reveal Proof, the server uses the blinded +share commitments, which do not expose the ciphertexts or blind +factors of the other shares (see [Why Per-Server Share Isolation]). + +Voters MUST distribute shares across multiple independent servers to +further limit any single server's view of their voting activity. +Server selection, temporal mixing, and communication protocols are +specified in [^submission-server]. + + +## Tally + +After the voting window closes, the vote chain MUST decrypt the per- +$(\mathsf{proposal}\_\mathsf{id}, \mathsf{vote}\_\mathsf{decision})$ +aggregate ciphertexts using the threshold decryption procedure +specified in [^ea-ceremony]. + + +## Vote Chain + +The vote chain MUST maintain the following state per voting round: + +- The **Vote Commitment Tree** as defined in [Vote Commitment Tree]. +- Three disjoint **nullifier sets** as defined in [Nullifier Sets]. +- A **per-$(\mathsf{proposal}\_\mathsf{id}, \mathsf{vote}\_\mathsf{decision})$ + encrypted share accumulator**: the running component-wise sum of + revealed El Gamal ciphertexts. + +For each transaction type, the vote chain MUST verify the +corresponding proof and perform the out-of-circuit checks specified in +[Delegation Proof], [Vote Proof], or [Vote Reveal Proof] respectively. + +The vote chain's consensus mechanism, block structure, transaction +encoding, and API are out of scope for this ZIP. + +### Poseidon Instantiation + +All Poseidon hashes in this protocol use the same instantiation as +the nullifier non-membership tree defined in [^balance-proof]: +$\mathsf{P128Pow5T3}$ over $\mathbb{F}_{q_{\mathbb{P}}}$ (the Pallas +base field), with S-box $x^5$, width $t = 3$, rate $r = 2$, targeting +128-bit security, using the standard parameter generation procedure +from [^poseidon]. + +Hashes with $L$ inputs use $\mathsf{ConstantLength}\langle L \rangle$ +mode (absorbing $L$ field elements with length padding), absorbing +two elements per permutation. + +The following table lists every Poseidon call site in this protocol: + +| Hash | Inputs ($L$) | Mode | Permutations | +|---|---|---|---| +| Voting round identifier | 8 | $\mathsf{ConstantLength}\langle 8 \rangle$ | 4 | +| VAN core | 6 | $\mathsf{ConstantLength}\langle 6 \rangle$ | 3 | +| VAN blinding | 2 | $\mathsf{ConstantLength}\langle 2 \rangle$ | 1 | +| VAN nullifier | 4 | $\mathsf{ConstantLength}\langle 4 \rangle$ | 2 | +| Vote commitment | 5 | $\mathsf{ConstantLength}\langle 5 \rangle$ | 3 | +| Blinded share commitment | 5 | $\mathsf{ConstantLength}\langle 5 \rangle$ | 3 | +| Shares hash | 16 | $\mathsf{ConstantLength}\langle 16 \rangle$ | 8 | +| Share nullifier | 4 | $\mathsf{ConstantLength}\langle 4 \rangle$ | 2 | +| VCT internal node | 2 | $\mathsf{ConstantLength}\langle 2 \rangle$ | 1 | +| Governance nullifier | 4 | $\mathsf{ConstantLength}\langle 4 \rangle$ | 2 | +| Rho binding | 7 | $\mathsf{ConstantLength}\langle 7 \rangle$ | 4 | + +### Domain Separator Tags + +Several constructions in this protocol use a domain separator tag +encoded as a Pallas scalar. Each tag is defined by a fixed ASCII +string. To convert a tag string to a field element, interpret its +byte representation as an unsigned little-endian integer: + +$$\mathsf{tag} = \sum_{j=0}^{\ell-1} b_j \cdot 256^j$$ + +where $b_0, \ldots, b_{\ell-1}$ are the ASCII byte values of the string +and $\ell$ is its length. All tag strings in this protocol are shorter +than 32 bytes, so the resulting integer is less than $2^{256}$ and fits +in $\mathbb{F}_{q_{\mathbb{P}}}$ without reduction. + +# Rationale + +## Why a Separate Vote Chain + +Voting transactions (delegation proofs, vote proofs, share reveals) are +not standard Zcash shielded transactions. They require a new commitment +tree (the VCT), new nullifier sets, and an encrypted share accumulator +with homomorphic aggregation. Implementing these as a sidechain avoids +modifying the Zcash consensus layer and keeps the governance mechanism +independent of mainchain upgrade cycles. + +## Why Poseidon for the VCT + +The Orchard note commitment tree uses Sinsemilla [^protocol-concretesinsemilla] +for Merkle hashing. The VCT uses Poseidon instead because all three ZKPs +in this protocol require VCT Merkle membership proofs, and Poseidon +operates natively on field elements, making it significantly more +efficient inside Halo 2 [^halo2] arithmetic circuits than Sinsemilla +(which is optimized for bitstring +inputs). Since the VCT is new infrastructure with no backwards- +compatibility constraint, the more circuit-efficient primitive is +appropriate. This is the same rationale as for the nullifier +non-membership tree in [^balance-proof]. + +## Why Ballot Scaling + +Expressing vote values in ballots ($\lfloor \text{zatoshi} / 12{,}500{,}000 \rfloor$) rather +than raw zatoshi reduces bit-width throughout the protocol: El Gamal +scalar multiplications are faster, range checks are tighter, and the +discrete-log recovery at tally time [^ea-ceremony] has a smaller +search space. +The 0.125 ZEC minimum also prevents dust delegations from bloating vote +chain state. + +## Why a 24-bit Remainder Range + +The remainder in the ballot scaling constraint is range-checked to +24 bits ($< 16{,}777{,}216$), which is wider than the divisor +($12{,}500{,}000$). A prover can set $r > 12{,}500{,}000$, effectively +shorting themselves one ballot. This is harmless: +$\mathsf{num}\_\mathsf{ballots}$ does not appear in any governance +nullifier, so the only effect is the prover voting with slightly less +weight than they could. The wider check avoids a custom +non-power-of-two range check in circuit. + +## Why Delegation to a Hotkey + +The protocol requires voters to produce Halo 2 zero-knowledge proofs +(delegation proof, vote proof) and construct vote commitments, operations that demand general-purpose computation on private key +material. Hardware wallets that custody Orchard spending keys cannot +perform these operations: they support signature generation but not +arbitrary-circuit ZKP construction. + +Delegation to a governance hotkey resolves this by separating spend +authorization from vote execution. The hardware wallet signs a single +$\mathsf{SpendAuthSig}^{\mathsf{Orchard}}$ during delegation, authorizing the +transfer of voting power to a software-controlled hotkey. All subsequent +voting operations (VAN consumption, vote commitment construction, share +submission) use the hotkey's key material and run on a general-purpose +device. + +Without this separation, hardware wallet users would need to either +export spending keys to a software environment, negating the security +benefit of hardware custody, or forgo participation in governance +entirely. Delegation preserves the hardware wallet's role as the sole +custodian of spending keys while enabling full participation in the +voting protocol. + +## Why Deterministic Hotkey Derivation + +The voting flow spans multiple app sessions: delegation (ZKP #1), +one or more votes (ZKP #2), and vote signing. Each step requires +the hotkey's spend-authorizing key. The per-vote secrets ($r_i$, +$\mathsf{blind}\_\mathsf{i}$, $\alpha_v$) are also derived +deterministically from the hotkey seed via a domain-separated PRF +(see [Per-Vote Secret Derivation]), so crash recovery extends to +all secrets needed for vote construction. + +Deriving every secret from a single seed means the wallet stores +only a BIP 39 mnemonic in its keychain. If the app is terminated +between delegation and voting, or between proposals, all key +material is reconstructed from the mnemonic. Randomly sampled keys +would require securely persisting each component ($\mathsf{vsk}$, +$\mathsf{vsk.nk}$, $\mathsf{rivk}\_\mathsf{v}$, and all per-vote +$r_i$ and $\mathsf{blind}\_\mathsf{i}$ values) independently, +and any storage failure would be unrecoverable. + +## Why VAN Nullifier Domain Separation + +The VAN nullifier uses $\mathsf{vsk.nk}$ (the governance hotkey's +nullifier deriving key) as the Poseidon key, while the governance +nullifier uses $\mathsf{nk}$ (the holder's nullifier deriving key). +When the hotkey is a separate key from the holder's, these are distinct +field elements, and cross-circuit collision resistance follows from the +key difference alone. When the hotkey reuses the holder's key hierarchy +(e.g., in an all-software flow without hardware wallet separation), +$\mathsf{vsk.nk} = \mathsf{nk}$ and collision resistance relies on +the domain tags being distinct: `"vote authority spend"` and +`"governance authorization"` differ in both byte length and content, +producing distinct field elements. The domain tags provide +defense-in-depth in both cases. + +## Why 5 Notes per Delegation + +The Delegation Proof fixes the note slot count at 5 (with padding for +holders who have fewer notes). This choice balances wallet coverage +against proof cost: empirical analysis of the Orchard shielded pool +shows that over 90% of wallets hold 5 or fewer notes, so most holders +can delegate their full balance with a single user-facing signature. + +Each additional note slot adds a full set of per-note constraints to +the circuit, increasing proving time. A higher +slot count would cover marginally more wallets at a disproportionate +cost in prover resources. Holders with more than 5 notes can perform multiple delegations, each covering up to 5 notes. + +## Why a Dummy Signed Note + +The Delegation Proof includes a dummy signed note (value 0) whose rho is +deterministically bound to the delegation context. This mechanism exists +to obtain a $\mathsf{SpendAuthSig}^{\mathsf{Orchard}}$ from hardware +wallets that support only the standard Orchard PCZT signing flow, +without requiring firmware changes specific to governance. + +The dummy signed note construction, PCZT signing flow, and design +rationale (1-zatoshi display value, rho binding for non-replayability, +ZIP 244 sighash reuse, and the trade-offs of a future custom signing +protocol) are specified in [^balance-proof]. + +## Why $N_s$ Shares Per Vote + +Splitting a vote into $N_s$ shares serves two purposes. First, it +provides temporal unlinkability: shares are submitted independently at +client-chosen times spread across the voting window, preventing an +observer from attributing all shares to a single voter by timing +correlation. Second, it limits the election authority's view: even if +the EA decrypts individual ciphertexts, it sees only individual shares, +not a voter's complete ballot allocation. + +When a voter casts near the end of the voting window, the protocol +falls back to single-share mode: the full ballot count is placed in one +share and submitted immediately. This sacrifices both benefits above but +ensures the vote is counted — each share requires the server to +construct a computationally expensive Vote Reveal Proof, and with +insufficient time remaining the server may not complete all $N_s$ +proofs before the deadline. See [^submission-server] for the +last-moment buffer definition and timing details. + +## Why Blinded Share Commitments + +Each share commitment includes a random blind factor: +$\mathsf{share}\_{\mathsf{comm}\_\mathsf{i}} = \mathsf{Poseidon}(\mathsf{blind}\_\mathsf{i}, C_{1,i,x}, C_{2,i,x}, C_{1,i,y}, C_{2,i,y})$. +Without blinding, an observer could compute +$\mathsf{Poseidon}(C_{1,i,x}, C_{2,i,x}, C_{1,i,y}, C_{2,i,y})$ for each on-chain ciphertext +and compare against the $\mathsf{shares}\_\mathsf{hash}$ values committed in +VCs, linking revealed shares back to specific vote commitments. The +blind factor makes this reverse computation infeasible. + +## Why Share Commitments Bind Full Curve Points + +On the Pallas curve, every $x$-coordinate has two valid $y$-values: +$P$ and $-P$. If the share commitment bound only the $x$-coordinates +of the ciphertext points, a malicious block proposer could negate +$C_1$ and $C_2$ by flipping the sign bits in their compressed +encodings (2 bit flips) without changing the $x$-coordinates. The +negated ciphertext encrypts $-v$ instead of $v$ under the same +El Gamal key, so the tally would accumulate $\mathsf{Enc}(-v)$ instead +of $\mathsf{Enc}(v)$, corrupting the election result. The Vote Reveal +Proof would still verify because the share commitment — binding only +$x$-coordinates — would be unchanged. + +Including both $x$- and $y$-coordinates in the share commitment hash binds each commitment to +the exact curve point. Flipping a sign bit changes the $y$-coordinate, +producing a different $\mathsf{share}\_\mathsf{comm}$, which cascades +through $\mathsf{shares}\_\mathsf{hash} \to \mathsf{vc} \to$ Merkle +root, invalidating the proof. + +Full $y$-coordinates are used rather than 1-bit sign values because +the $y$-cells are already available from the ECC gadget output in the +Vote Proof circuit; extracting parity bits in-circuit would require a +255-bit field decomposition gadget, adding substantial constraint cost +for no security benefit. + +## Why Per-Server Share Isolation + +Each submission server receives only the ciphertext and blind factor +for the single share it reveals, plus the blinded share commitments +for all $N_s$ shares. It does not receive the raw ciphertexts or blind +factors of shares assigned to other servers. This limits the data any +single server can observe: even a compromised server learns only one +share's plaintext-encrypted ciphertext and blind, not the full set. +The blinded share commitments are sufficient for the server to +recompute $\mathsf{shares}\_\mathsf{hash}$ in the proof (condition 3), +while the blinding prevents the server from correlating other shares' +ciphertexts with their commitments. + +## Why Server-Delegated Share Reveal + +The Vote Reveal Proof is constructed by the submission server +rather than the voter's client for two reasons: mobile devices are +unreliable for background ZKP computation, and server-side construction +enables temporal mixing of shares from many voters. The trust +requirement on the server is minimal: it learns encrypted shares and +vote decisions but cannot decrypt amounts or link shares to identities. + +## Why Reusing VAN Address and Randomness + +When a vote consumes a VAN and produces a new one, the new VAN reuses +the old VAN's diversified address ($\mathsf{vpk}\_{\mathsf{g}\_\mathsf{d}}$, +$\mathsf{vpk}\_{\mathsf{pk}\_\mathsf{d}}$) and commitment randomness +($\mathsf{gov}\_{\mathsf{comm}\_\mathsf{rand}}$). Only +$\mathsf{proposal}\_\mathsf{authority}$ changes. This is safe because VAN +commitments are blinded Poseidon hashes - the shared fields are never +externally observable (both old and new commitments appear as opaque +field elements in the VCT), so address rotation would provide no +additional unlinkability. + +## Why Proposal Identifiers Start at 1 + +The $\mathsf{proposal}\_\mathsf{authority}$ bitmask is 16 bits wide, but +$\mathsf{proposal}\_\mathsf{id}$ values start at 1, yielding 15 usable +proposal slots rather than 16. Bit 0 is reserved. + +This follows from how lookup arguments work in Halo 2. A lookup table +that validates $(\mathsf{proposal}\_\mathsf{id}, 2^{\mathsf{proposal}\_\mathsf{id}})$ +must include a default row that satisfies the lookup when the selector +is inactive. The circuit uses the identity row $(0, 1)$ for this +purpose: when the selector $q = 0$, the lookup input evaluates to +$(0, 1)$, which must be present in the table for the proof to verify. +Because every inactive row matches this entry, $\mathsf{proposal}\_\mathsf{id} = 0$ +cannot be used as a valid proposal identifier: a prover could trivially +satisfy the lookup with $\mathsf{proposal}\_\mathsf{id} = 0$ even when no +authority check is intended. An additional non-zero gate +($\mathsf{proposal}\_\mathsf{id} \cdot \mathsf{proposal}\_\mathsf{id}^{-1} = 1$) +provides defense-in-depth by rejecting $\mathsf{proposal}\_\mathsf{id} = 0$ +on active rows. + +## Why Threshold Secret Sharing + +See [^ea-ceremony] for the rationale behind threshold secret sharing. + +## Why a Send-Based VAN Model + +The Vote Proof consumes the old VAN and produces a new one with the +voted proposal's authority bit cleared. This is a UTXO-style +"send": each vote appends two leaves to the VCT (new VAN + VC) and +requires the voter to hold a current Merkle path. + +An alternative design was considered in which the VAN is eliminated +entirely and delegation is performed by out-of-band sharing of a +private key (hash-committed to on-chain). Under this model the Vote +Proof would not consume or re-create a VAN, removing the proposal +authority decrement step and the corresponding VCT growth. The primary +appeal is reduced client sync overhead: voters would not need to track +VAN re-insertions or maintain up-to-date Merkle paths for their own +VANs. + +The send-based model is retained because it preserves the ability to +add partial delegation in a future extension, splitting +$\mathsf{num}\_\mathsf{ballots}$ across multiple delegates, each +receiving a fraction of the holder's voting weight. The VAN's explicit +ballot count and proposal authority bitmask are the data model that +would enable this; a future VAN-to-VAN delegation proof could consume +one VAN and produce two with subdivided ballot counts. Partial +delegation is not specified in this ZIP but the VAN model keeps the +design space open. Under the keysharing alternative, the delegate holds +the full key and therefore the full voting weight; there is no +in-protocol mechanism to subdivide it, and adding one later would +require a fundamentally different data model. + +The sync savings of the alternative are also smaller than they first +appear: even without VAN re-creation, clients still need to update +VCT Merkle paths for their Vote Commitments (which the submission +server requires for the Vote Reveal Proof). The VAN model adds +incremental path-update overhead but does not introduce a new +category of sync obligation. If a future design change eliminated the +need for clients to track VCT paths entirely (for example by moving +Merkle path retrieval fully to the submission server), the tradeoff +would shift in favor of removing the VAN. See [Open issues]. + +## Why Classical El Gamal Rather Than Post-Quantum Encryption + +See [^ea-ceremony] for the rationale behind using classical El Gamal +rather than post-quantum encryption. The key consequence for this +protocol is that vote-amount privacy has a finite horizon tied to +quantum computing timelines, while voter *identity* is unaffected +(alternate nullifier unlinkability relies on Poseidon preimage +resistance, not on El Gamal). Vote splitting across $N_s$ shares +provides additional mitigation: a quantum adversary would recover +individual shares rather than complete ballot allocations unless it +also breaks the Poseidon-based blinded share commitments. + +## Why VCT Depth 24 + +The Orchard note commitment tree uses depth 32, supporting $2^{32}$ +(~4.3 billion) leaves. Governance voting produces far fewer leaves: +each voter generates one VAN per delegation and two leaves (a new VAN +plus a VC) per vote. Even 10,000 voters each voting on 50 proposals +produce roughly 1 million leaves, well within the $2^{24}$ +(~16.7 million) capacity of a depth-24 tree. Because each voting round +maintains its own VCT, this budget applies per round rather than +accumulating across rounds. + +The reduced depth saves constraint rows in every circuit that performs +a Merkle membership proof (Vote Proof and Vote Reveal Proof), since +each level adds a Poseidon hash region. Moving from 32 to 24 levels +removes 8 hash regions per proof, reducing prover cost and verification +time without any practical capacity risk. + + +## Why Domain Tags in the VCT + +Both VANs and VCs are leaves in the same Merkle tree. The domain tags +($\mathsf{DOMAIN}\_\mathsf{VAN} = 0$, $\mathsf{DOMAIN}\_\mathsf{VC} = 1$) as the first +Poseidon input make it structurally impossible for a valid VAN preimage +to produce the same hash as a valid VC preimage, regardless of the +remaining inputs. + + +# Deployment + +This ZIP does not specify a consensus change to the Zcash mainchain. +Deployment considerations are specific to the vote chain and will be +addressed in the operational voting process ZIP. + +construction. + + +# Reference implementation + +- [^ref-circuits] — Halo 2 circuits for the Delegation Proof, Vote + Proof, and Vote Reveal Proof. +- [^ref-vote-sdk] — Cosmos SDK vote chain implementing the VCT, + nullifier sets, encrypted share accumulator, ceremony, tally, and + submission server. +- [^ref-nullifier-pir] — PIR server and client for privately retrieving + nullifier non-membership proofs. +- [^ref-librustvoting] — Client-side Rust library for proof generation, + vote construction, tree synchronization, and governance PCZT + construction. + + +# Open issues + +- A future custom signing protocol purpose-built for proof-of-balance + could simplify the Delegation Proof circuit by removing the dummy + signed note scaffolding and enable further improvements described in + [^balance-proof]. +- Partial delegation (a VAN-to-VAN delegation proof that consumes + one VAN and produces two with subdivided $\mathsf{num}\_\mathsf{ballots}$) + is enabled by the send-based VAN model but not specified in this + ZIP. Specifying the circuit and transaction type would allow a + holder to distribute voting weight across multiple delegates. + See [Why a Send-Based VAN Model]. +- A simplified non-send VAN model, replacing VAN consumption and + re-creation with out-of-band key delegation, would remove the + proposal authority decrement step from the Vote Proof and reduce + per-vote VCT growth. This is currently not adopted because it + forecloses partial delegation and clients still need VCT Merkle path + updates regardless. If future design changes remove the client's need + to track VCT paths (e.g., full server-side path retrieval), this + tradeoff should be revisited. + See [Why a Send-Based VAN Model]. +- Open issues related to the EA key ceremony are tracked in [^ea-ceremony]. +- Open issues related to the submission server (share decomposition + strategy, client confirmation via PIR, balance amendment, decision + encryption) are tracked in [^submission-server]. +- Open issues related to the balance proof are tracked in [^balance-proof]. + + +# References + +[^BCP14]: [Information on BCP 14 — "RFC 2119: Key words for use in RFCs to Indicate Requirement Levels" and "RFC 8174: Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words"](https://www.rfc-editor.org/info/bcp14) + +[^protocol]: [Zcash Protocol Specification, Version 2025.6.3 [NU6.1] or later](protocol/protocol.pdf) + +[^protocol-concretespendauthsig]: [Zcash Protocol Specification, Version 2025.6.3 [NU6.1]. Section 5.4.7.1: Spend Authorization Signature (Orchard)](protocol/protocol.pdf#concretespendauthsig) + +[^protocol-orchardkeycomponents]: [Zcash Protocol Specification, Version 2025.6.3 [NU6.1]. Section 4.2.3: Orchard Key Components](protocol/protocol.pdf#orchardkeycomponents) + +[^bip39]: [M. Palatinus, P. Rusnak, A. Voisine, and S. Bowe, "BIP 39: Mnemonic code for generating deterministic keys", 2013](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) + +[^poseidon]: [Poseidon: A New Hash Function for Zero-Knowledge Proof Systems](https://eprint.iacr.org/2019/458) + +[^balance-proof]: [Orchard Proof-of-Balance](draft-valargroup-orchard-balance-proof) + +[^pir-governance]: [Private Information Retrieval for Nullifier Exclusion Proofs](draft-valargroup-nullifier-pir) + +[^ea-ceremony]: [Election Authority Key Ceremony](draft-valargroup-ea-key-ceremony) + +[^voting-setup]: [Zcash Shielded Coinholder Voting](draft-valargroup-shielded-voting-setup) + +[^submission-server]: [Vote Share Submission Server](draft-valargroup-submission-server) + +[^halo2]: [S. Bowe, J. Grigg, and D. Hopwood, "Recursive Proof Composition without a Trusted Setup", 2019](https://eprint.iacr.org/2019/1021) + +[^protocol-merkletree]: [Zcash Protocol Specification, Version 2025.6.3 [NU6.1]. Section 3.8: Note Commitment Trees](protocol/protocol.pdf#merkletree) + +[^protocol-concretesinsemilla]: [Zcash Protocol Specification, Version 2025.6.3 [NU6.1]. Section 5.4.1.9: Sinsemilla Hash Function](protocol/protocol.pdf#concretesinsemillahash) + +[^protocol-concretecommitivk]: [Zcash Protocol Specification, Version 2025.6.3 [NU6.1]. Section 5.4.9.4: CommitIvk](protocol/protocol.pdf#concretecommitivk) + +[^protocol-pallasandvesta]: [Zcash Protocol Specification, Version 2025.6.3 [NU6.1]. Section 5.4.9.6: Pallas and Vesta](protocol/protocol.pdf#pallasandvesta) + +[^zip-244]: [ZIP 244: Transaction Identifier and Signature Validation for v5 Transactions](zip-0244) + +[^pczt]: [zcash/zips issue #693: Standardize a protocol for creating shielded transactions offline (PCZT)](https://github.com/zcash/zips/issues/693) + +[^ref-circuits]: [valargroup/voting-circuits: Halo 2 ZKP circuits for shielded voting](https://github.com/valargroup/voting-circuits) + +[^ref-vote-sdk]: [valargroup/vote-sdk: Cosmos SDK vote chain for shielded voting](https://github.com/valargroup/vote-sdk) + +[^ref-nullifier-pir]: [valargroup/vote-nullifier-pir: PIR system for nullifier non-membership proofs](https://github.com/valargroup/vote-nullifier-pir) + +[^ref-librustvoting]: [valargroup/librustvoting: Client-side voting library for proof generation and vote construction](https://github.com/valargroup/librustvoting)