PR #1200proposed changes to·draft-valargroup-shielded-voting

[ZIP TBD] Shielded Voting Protocol

status → Draftby p0mvn · updated 2026-08-18zips/draft-valargroup-shielded-voting.md @ 7b1ef40df9 · upstream 4699a8d518view on GitHub ↗

Unofficial preview — the proposed text below is rendered from the pull request head and is not part of the canonical ZIP set.

Contents

Terminology

The key words "MUST", "REQUIRED", "MUST NOT", "SHOULD", and "MAY" in this document are to be interpreted as described in BCP 14 1 when, and only when, they appear in all capitals.

The character § is used when referring to sections of the Zcash Protocol Specification. 2

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 Ns 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 𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽 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 𝖾𝖺_𝗌𝗄 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 3) 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 3) 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 Ns 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 3 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 𝗇𝗄. This follows from the alternate nullifier unlinkability property established in 3.

Balance hiding via vote splitting. A voter's total ballot count is decomposed into Ns 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 𝗌𝗁𝖺𝗋𝖾𝗌_𝗁𝖺𝗌𝗁 from on-chain ciphertexts and linking revealed shares back to a specific VC.

Trust assumptions. After the key ceremony, no single party holds 𝖾𝖺_𝗌𝗄; each validator holds only a Shamir share. An adversary must compromise at least t validators (where t=⌈n/2⌉+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 4.

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 5.

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 6 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 𝖾𝖺_𝗌𝗄 shares) is specified separately in 7.
  • The operational process for conducting a coinholder vote (validator setup, poll creation, deadlines) is out of scope; it is specified in 8.
  • 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 5.

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 3) 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 Ns 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 4. Each payload includes a client-chosen 𝗌𝗎𝖻𝗆𝗂𝗍_𝖺𝗍 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 4), 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 7). 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 7. This ZIP uses the notation established there: G is the Pallas 𝖲𝗉𝖾𝗇𝖽𝖠𝗎𝗍𝗁𝖲𝗂𝗀𝖮𝗋𝖼𝗁𝖺𝗋𝖽 generator 9, 𝖾𝖺_𝗉𝗄=[𝖾𝖺_𝗌𝗄]G is the election authority's public key for the current voting round, and a ciphertext is a pair of Pallas points (C1,C2) where 𝖤𝗇𝖼(v,r)=([r]G,[v]G+[r]𝖾𝖺_𝗉𝗄).

Data Structures

Voting Round Identifier

Each voting round is identified by a 32-byte 𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽, 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.

𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽=𝖯𝗈𝗌𝖾𝗂𝖽𝗈𝗇(𝗌𝗇𝖺𝗉𝗌𝗁𝗈𝗍_𝗁𝖾𝗂𝗀𝗁𝗍, 𝖻𝗁_𝗅𝗈, 𝖻𝗁_𝗁𝗂, 𝗉𝗁_𝗅𝗈, 𝗉𝗁_𝗁𝗂, 𝗏𝗈𝗍𝖾_𝖾𝗇𝖽_𝗍𝗂𝗆𝖾, 𝗇𝗎𝗅𝗅𝗂𝖿𝗂𝖾𝗋_𝗂𝗆𝗍_𝗋𝗈𝗈𝗍, 𝗇𝖼_𝗋𝗈𝗈𝗍)

The hash uses the 𝖢𝗈𝗇𝗌𝗍𝖺𝗇𝗍𝖫𝖾𝗇𝗀𝗍𝗁⟨8⟩ variant of the Poseidon instantiation specified in [Poseidon Instantiation].

where:

  • 𝗌𝗇𝖺𝗉𝗌𝗁𝗈𝗍_𝗁𝖾𝗂𝗀𝗁𝗍∈{0..264−1} — the Zcash mainnet block height of the chosen snapshot, encoded as a Pallas base field element.
  • 𝖻𝗁_𝗅𝗈,𝖻𝗁_𝗁𝗂∈{0..2128−1} — the low and high 128-bit halves of 𝗌𝗇𝖺𝗉𝗌𝗁𝗈𝗍_𝖻𝗅𝗈𝖼𝗄𝗁𝖺𝗌𝗁 in little-endian byte order, each encoded as a Pallas base field element.
  • 𝗉𝗁_𝗅𝗈,𝗉𝗁_𝗁𝗂∈{0..2128−1} — the low and high 128-bit halves of 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅𝗌_𝗁𝖺𝗌𝗁 in little-endian byte order.
  • 𝗏𝗈𝗍𝖾_𝖾𝗇𝖽_𝗍𝗂𝗆𝖾∈{0..264−1} — Unix timestamp (seconds) after which votes are no longer accepted, encoded as a Pallas base field element.
  • 𝗇𝗎𝗅𝗅𝗂𝖿𝗂𝖾𝗋_𝗂𝗆𝗍_𝗋𝗈𝗈𝗍∈{0..qℙ−1} — root of the nullifier non-membership IMT at the snapshot height. MUST be a canonical Pallas base field element.
  • 𝗇𝖼_𝗋𝗈𝗈𝗍∈{0..qℙ−1} — Orchard note commitment tree root at the snapshot height. MUST be a canonical Pallas base field element.

𝗌𝗇𝖺𝗉𝗌𝗁𝗈𝗍_𝖻𝗅𝗈𝖼𝗄𝗁𝖺𝗌𝗁 and 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅𝗌_𝗁𝖺𝗌𝗁 are split into two 128-bit limbs because they are not necessarily canonical Pallas field elements. 𝗇𝗎𝗅𝗅𝗂𝖿𝗂𝖾𝗋_𝗂𝗆𝗍_𝗋𝗈𝗈𝗍 and 𝗇𝖼_𝗋𝗈𝗈𝗍 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:

𝗏𝖺𝗇_𝖼𝗈𝗋𝖾=𝖯𝗈𝗌𝖾𝗂𝖽𝗈𝗇(𝖣𝖮𝖬𝖠𝖨𝖭_𝖵𝖠𝖭,𝗏𝗉𝗄_𝗀_𝖽,𝗏𝗉𝗄_𝗉𝗄_𝖽,𝗇𝗎𝗆_𝖻𝖺𝗅𝗅𝗈𝗍𝗌,𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽,𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝖺𝗎𝗍𝗁𝗈𝗋𝗂𝗍𝗒)

𝗏𝖺𝗇=𝖯𝗈𝗌𝖾𝗂𝖽𝗈𝗇(𝗏𝖺𝗇_𝖼𝗈𝗋𝖾,𝗀𝗈𝗏_𝖼𝗈𝗆𝗆_𝗋𝖺𝗇𝖽)

The first layer binds the structural fields; the second layer blinds the commitment with randomness. These are two separate Poseidon invocations (𝖢𝗈𝗇𝗌𝗍𝖺𝗇𝗍𝖫𝖾𝗇𝗀𝗍𝗁⟨6⟩ then 𝖢𝗈𝗇𝗌𝗍𝖺𝗇𝗍𝖫𝖾𝗇𝗀𝗍𝗁⟨2⟩), not a single 7-input sponge absorption.

where:

  • 𝖣𝖮𝖬𝖠𝖨𝖭_𝖵𝖠𝖭=0 — domain tag distinguishing VANs from VCs in the shared VCT.
  • 𝗏𝗉𝗄_𝗀_𝖽∈{0..qℙ−1} — 𝖤𝗑𝗍𝗋𝖺𝖼𝗍_ℙ of the diversified base of the governance hotkey address.
  • 𝗏𝗉𝗄_𝗉𝗄_𝖽∈{0..qℙ−1} — 𝖤𝗑𝗍𝗋𝖺𝖼𝗍_ℙ of the diversified transmission key of the governance hotkey address.
  • 𝗇𝗎𝗆_𝖻𝖺𝗅𝗅𝗈𝗍𝗌∈{1…230} — total voting weight in ballots.
  • 𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽∈{0..qℙ−1} — scopes this VAN to a specific voting round.
  • 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝖺𝗎𝗍𝗁𝗈𝗋𝗂𝗍𝗒∈{0…216−1} — bitmask encoding which proposals this VAN is authorized to vote on. 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽 values 1–15 map to bits 1–15; bit 0 is reserved (see [Why Proposal Identifiers Start at 1]). Full authority is 216−1=65535.
  • 𝗀𝗈𝗏_𝖼𝗈𝗆𝗆_𝗋𝖺𝗇𝖽∈{0..qℙ−1} — commitment randomness.

VAN nullifier. When a VAN is consumed (to vote or delegate), its nullifier is:

𝗏𝖺𝗇_𝗇𝗎𝗅𝗅𝗂𝖿𝗂𝖾𝗋=𝖯𝗈𝗌𝖾𝗂𝖽𝗈𝗇(𝗏𝗌𝗄.𝗇𝗄,𝗍𝖺𝗀𝗏𝖺𝗇,𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽,𝗏𝖺𝗇)

where 𝗍𝖺𝗀𝗏𝖺𝗇 is the field-element encoding of the domain separator "vote authority spend" and 𝗏𝗌𝗄.𝗇𝗄 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 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝖺𝗎𝗍𝗁𝗈𝗋𝗂𝗍𝗒.

The VAN model is designed to support future extensions such as partial delegation (splitting 𝗇𝗎𝗆_𝖻𝖺𝗅𝗅𝗈𝗍𝗌 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:

𝗏𝖼=𝖯𝗈𝗌𝖾𝗂𝖽𝗈𝗇(𝖣𝖮𝖬𝖠𝖨𝖭_𝖵𝖢,𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽,𝗌𝗁𝖺𝗋𝖾𝗌_𝗁𝖺𝗌𝗁,𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽,𝗏𝗈𝗍𝖾_𝖽𝖾𝖼𝗂𝗌𝗂𝗈𝗇)

where:

  • 𝖣𝖮𝖬𝖠𝖨𝖭_𝖵𝖢=1 — domain tag.
  • 𝗌𝗁𝖺𝗋𝖾𝗌_𝗁𝖺𝗌𝗁∈{0..qℙ−1} — a hash over blinded commitments to all Ns encrypted shares (see [Shares Hash]).
  • 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽∈{1…15} — which proposal this vote targets.
  • 𝗏𝗈𝗍𝖾_𝖽𝖾𝖼𝗂𝗌𝗂𝗈𝗇∈{0..qℙ−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 (𝗌𝗁𝖺𝗋𝖾𝗌_𝗁𝖺𝗌𝗁 and 𝗏𝗈𝗍𝖾_𝖽𝖾𝖼𝗂𝗌𝗂𝗈𝗇) 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 Ns=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 𝗇𝗎𝗆_𝖻𝖺𝗅𝗅𝗈𝗍𝗌 and that each share is in [0,230); 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 4.

For share index i∈{0…Ns−1}:

  • 𝗏_𝗂∈{0…230−1} — plaintext share amount in ballots (private, never revealed).
  • ri — El Gamal encryption randomness (Pallas scalar, private).
  • 𝖻𝗅𝗂𝗇𝖽_𝗂∈{0..qℙ−1} — per-share blind factor for the blinded share commitment (independent of ri).
  • 𝖾𝗇𝖼_𝗌𝗁𝖺𝗋𝖾_𝗂=𝖤𝗇𝖼(𝗏_𝗂,ri)=(C1,i,C2,i) — El Gamal ciphertext.

Blinded share commitment:

𝗌𝗁𝖺𝗋𝖾_𝖼𝗈𝗆𝗆_𝗂=𝖯𝗈𝗌𝖾𝗂𝖽𝗈𝗇(𝖻𝗅𝗂𝗇𝖽_𝗂,C1,i,x,C2,i,x,C1,i,y,C2,i,y)

where C1,i,x, C2,i,x denote the x-coordinates and C1,i,y, C2,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 Ns blinded share commitments:

𝗌𝗁𝖺𝗋𝖾𝗌_𝗁𝖺𝗌𝗁=𝖯𝗈𝗌𝖾𝗂𝖽𝗈𝗇(𝗌𝗁𝖺𝗋𝖾_𝖼𝗈𝗆𝗆_𝟢,𝗌𝗁𝖺𝗋𝖾_𝖼𝗈𝗆𝗆_𝟣,…,𝗌𝗁𝖺𝗋𝖾_𝖼𝗈𝗆𝗆Ns−1)

This is an Ns-input Poseidon sponge hash.

Share Nullifier

When a share is revealed, its nullifier is:

𝗌𝗁𝖺𝗋𝖾_𝗇𝗎𝗅𝗅𝗂𝖿𝗂𝖾𝗋=𝖯𝗈𝗌𝖾𝗂𝖽𝗈𝗇(𝗍𝖺𝗀𝗌𝗁𝖺𝗋𝖾,𝗏𝖼,𝗌𝗁𝖺𝗋𝖾_𝗂𝗇𝖽𝖾𝗑,𝖻𝗅𝗂𝗇𝖽)

where 𝗍𝖺𝗀𝗌𝗁𝖺𝗋𝖾 is the field-element encoding of "share spend", 𝗏𝖼 is the vote commitment (private), and 𝖻𝗅𝗂𝗇𝖽 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 10 of depth 𝖬𝖾𝗋𝗄𝗅𝖾𝖣𝖾𝗉𝗍𝗁𝗏𝖼𝗍=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 𝖣𝖮𝖬𝖠𝖨𝖭_𝖵𝖠𝖭=0 for VANs and 𝖣𝖮𝖬𝖠𝖨𝖭_𝖵𝖢=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 constantStringLength
𝗍𝖺𝗀_𝗀𝗈𝗏"governance authorization"24 bytes
𝗍𝖺𝗀_𝗏𝖺𝗇"vote authority spend"20 bytes
𝗍𝖺𝗀_𝗌𝗁𝖺𝗋𝖾"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 𝗏𝗌𝗄, from which 𝗏𝗌𝗄.𝖺𝗄=[𝗏𝗌𝗄]G is derived.
  • A nullifier deriving key 𝗏𝗌𝗄.𝗇𝗄, derived from the hotkey's spending key via the standard Orchard key hierarchy.
  • A CommitIvk trapdoor 𝗋𝗂𝗏𝗄_𝗏, 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 [𝗂𝗏𝗄_𝗏]𝗀_𝖽 with 𝗂𝗏𝗄_𝗏=𝖢𝗈𝗆𝗆𝗂𝗍𝖨𝗏𝗄_𝗋𝗂𝗏𝗄_𝗏(𝖤𝗑𝗍𝗋𝖺𝖼𝗍_ℙ(𝗏𝗌𝗄.𝖺𝗄),𝗏𝗌𝗄.𝗇𝗄). The VAN commitment (see [Vote Authority Note (VAN)]) stores the x-coordinates 𝗏𝗉𝗄_𝗀_𝖽=𝖤𝗑𝗍𝗋𝖺𝖼𝗍_ℙ(𝗀_𝖽) and 𝗏𝗉𝗄_𝗉𝗄_𝖽=𝖤𝗑𝗍𝗋𝖺𝖼𝗍_ℙ(𝗉𝗄_𝖽); 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 𝗌𝖾𝖾𝖽_𝗏. The wallet MUST generate 𝗌𝖾𝖾𝖽_𝗏 by creating a fresh BIP 39 mnemonic 11, 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

𝗌𝗄_𝗏=𝖡𝗅𝖺𝗄𝖾𝟤𝖻-512("𝚉𝚌𝚊𝚜𝚑𝚅𝚘𝚝𝚒𝚗𝚐𝙷𝚘𝚝𝙺𝚢",𝗌𝖾𝖾𝖽_𝗏)

interpreted as a Pallas scalar via 𝖥𝗋𝗈𝗆𝖴𝗇𝗂𝖿𝗈𝗋𝗆𝖡𝗒𝗍𝖾𝗌, and then derives 𝗏𝗌𝗄.𝖺𝗄, 𝗏𝗌𝗄.𝗇𝗄, and 𝗋𝗂𝗏𝗄_𝗏 from 𝗌𝗄_𝗏 following § 4.2.3 'Orchard Key Components' 12. See [Why Deterministic Hotkey Derivation].

Per-Vote Secret Derivation

The El Gamal encryption randomness ri, per-share blind factors 𝖻𝗅𝗂𝗇𝖽_𝗂, and spend authorization randomizer α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 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽 consuming VAN commitment 𝗏𝖺𝗇_𝗈𝗅𝖽 in voting round 𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽, the wallet MUST derive a 64-byte per-vote root:

𝗏𝗈𝗍𝖾_𝗋𝗈𝗈𝗍=𝖡𝗅𝖺𝗄𝖾𝟤𝖻-512("𝚉𝚌𝚊𝚜𝚑𝚅𝚘𝚝𝚎𝚂𝚎𝚌𝚛𝚎𝚝",𝗌𝗄_𝗏‖𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽‖𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽‖𝗏𝖺𝗇_𝗈𝗅𝖽)

where ‖ denotes concatenation of canonical little-endian encodings (32 bytes for field elements, 1 byte for 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽).

From 𝗏𝗈𝗍𝖾_𝗋𝗈𝗈𝗍, the wallet MUST derive each per-share secret:

ri=𝖥𝗋𝗈𝗆𝖴𝗇𝗂𝖿𝗈𝗋𝗆𝖡𝗒𝗍𝖾𝗌(𝖡𝗅𝖺𝗄𝖾𝟤𝖻-512("𝚉𝚌𝚊𝚜𝚑𝚅𝚘𝚝𝚎𝙴𝚕𝙶𝚊𝚖",𝗏𝗈𝗍𝖾_𝗋𝗈𝗈𝗍‖i))

𝖻𝗅𝗂𝗇𝖽_𝗂=𝖥𝗋𝗈𝗆𝖴𝗇𝗂𝖿𝗈𝗋𝗆𝖡𝗒𝗍𝖾𝗌(𝖡𝗅𝖺𝗄𝖾𝟤𝖻-512("𝚉𝚌𝚊𝚜𝚑𝚅𝚘𝚝𝚎𝙱𝚕𝚒𝚗𝚍",𝗏𝗈𝗍𝖾_𝗋𝗈𝗈𝗍‖i))

where i is the share index encoded as a single byte. The spend authorization randomizer is:

αv=𝖥𝗋𝗈𝗆𝖴𝗇𝗂𝖿𝗈𝗋𝗆𝖡𝗒𝗍𝖾𝗌(𝖡𝗅𝖺𝗄𝖾𝟤𝖻-512("𝚉𝚌𝚊𝚜𝚑𝚅𝚘𝚝𝚎𝙰𝚕𝚙𝚑𝚊",𝗏𝗈𝗍𝖾_𝗋𝗈𝗈𝗍))

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. 𝖥𝗋𝗈𝗆𝖴𝗇𝗂𝖿𝗈𝗋𝗆𝖡𝗒𝗍𝖾𝗌 interprets a 64-byte input as a Pallas scalar. 13

See [Why Deterministic Hotkey Derivation].

Ballot Scaling

Orchard note values are denominated in zatoshi. The Delegation Proof MUST convert zatoshi to ballots:

𝗇𝗎𝗆_𝖻𝖺𝗅𝗅𝗈𝗍𝗌=⌊∑vi12,500,000⌋

where vi are the values of the delegated Orchard notes. One ballot equals 0.125 ZEC.

The prover MUST witness 𝗇𝗎𝗆_𝖻𝖺𝗅𝗅𝗈𝗍𝗌 and a remainder r. The Delegation Proof circuit MUST enforce:

  1. 𝗇𝗎𝗆_𝖻𝖺𝗅𝗅𝗈𝗍𝗌×12,500,000+r=∑vi
  2. 0≤r<224 (see [Why a 24-bit Remainder Range])
  3. 𝗇𝗎𝗆_𝖻𝖺𝗅𝗅𝗈𝗍𝗌≥1
  4. 𝗇𝗎𝗆_𝖻𝖺𝗅𝗅𝗈𝗍𝗌≤230

The 30-bit upper bound accommodates up to ≈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 3. This section specifies only the conditions that extend beyond the Balance Proof.

Public Inputs

Given a primary input:

  • 𝗌𝗂𝗀𝗇𝖾𝖽_𝗇𝗈𝗍𝖾_𝗇𝗎𝗅𝗅𝗂𝖿𝗂𝖾𝗋⦂{0..qℙ−1} — nullifier of the dummy signed note (for spend authorization binding).
  • 𝗋𝗄⦂𝖲𝗉𝖾𝗇𝖽𝖠𝗎𝗍𝗁𝖲𝗂𝗀𝖮𝗋𝖼𝗁𝖺𝗋𝖽.𝖯𝗎𝖻𝗅𝗂𝖼 — randomized spend authorization verification key.
  • 𝗋𝗍𝖼𝗆⦂{0..qℙ−1} — Orchard note commitment tree root at the snapshot height.
  • 𝗋𝗍𝖾𝗑𝖼𝗅⦂{0..qℙ−1} — nullifier non-membership tree root at the snapshot height.
  • 𝗏𝖺𝗇⦂{0..qℙ−1} — the initial VAN commitment.
  • 𝗀𝗈𝗏_𝗇𝗎𝗅𝗅_𝟣,…,𝗀𝗈𝗏_𝗇𝗎𝗅𝗅_𝟧⦂{0..qℙ−1} — governance nullifiers for each note slot.
  • 𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽⦂{0..qℙ−1}
  • 𝖼𝗆𝗑_𝗇𝖾𝗐⦂{0..qℙ−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 3:

  • 𝗏𝗉𝗄=(𝗏𝗉𝗄_𝗀_𝖽,𝗏𝗉𝗄_𝗉𝗄_𝖽) — governance hotkey diversified address.
  • 𝗀𝗈𝗏_𝖼𝗈𝗆𝗆_𝗋𝖺𝗇𝖽 — VAN commitment randomness.
  • Signed note data: 𝗀_𝖽𝗌𝗂𝗀𝗇𝖾𝖽,𝗉𝗄_𝖽𝗌𝗂𝗀𝗇𝖾𝖽,ρ𝗌𝗂𝗀𝗇𝖾𝖽,ψ𝗌𝗂𝗀𝗇𝖾𝖽,𝗋𝖼𝗆𝗌𝗂𝗀𝗇𝖾𝖽,𝖼𝗆𝗌𝗂𝗀𝗇𝖾𝖽 (value = 0).
  • Output note data (PCZT scaffolding): 𝗀_𝖽𝗇𝖾𝗐,𝗉𝗄_𝖽𝗇𝖾𝗐,𝗏𝗇𝖾𝗐,ρ𝗇𝖾𝗐,ψ𝗇𝖾𝗐,𝗋𝖼𝗆𝗇𝖾𝗐 (address = hotkey). 𝗏𝗇𝖾𝗐 is not constrained by the circuit.

Conditions

Per-note conditions (5 note slots, with padding). For each note i∈{1…5}, the circuit MUST enforce the following conditions from the Batched Claim circuit 3:

  • Note commitment integrity.
  • Merkle path validity in 𝗋𝗍𝖼𝗆 (skipped for padded notes).
  • Diversified address integrity (same 𝗂𝗏𝗄 MUST own all notes).
  • Standard nullifier derivation (kept private).
  • Nullifier non-membership in 𝗋𝗍𝖾𝗑𝖼𝗅 (skipped for padded notes).
  • Padded notes MUST have value 0.

Governance nullifier derivation. For each real note i, the circuit MUST enforce:

𝗀𝗈𝗏_𝗇𝗎𝗅𝗅_𝗂=𝖯𝗈𝗌𝖾𝗂𝖽𝗈𝗇(𝗇𝗄,𝗍𝖺𝗀𝗀𝗈𝗏,𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽,𝗇𝖿i𝗈𝗅𝖽)

where 𝗍𝖺𝗀𝗀𝗈𝗏 is the field-element encoding of "governance authorization" and 𝗇𝖿i𝗈𝗅𝖽 is the note's standard nullifier (computed in-circuit but never revealed). This is an instantiation of the alternate nullifier derivation defined in 3, with 𝗍𝖺𝗀=𝗍𝖺𝗀𝗀𝗈𝗏 and 𝖽𝗈𝗆=𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽.

Signed note integrity. The signed note MUST be a dummy note with value 0. The circuit MUST enforce that 𝖼𝗆𝗌𝗂𝗀𝗇𝖾𝖽 is correctly constructed and that 𝗌𝗂𝗀𝗇𝖾𝖽_𝗇𝗈𝗍𝖾_𝗇𝗎𝗅𝗅𝗂𝖿𝗂𝖾𝗋 is correctly derived from it.

Rho binding. The circuit MUST enforce that ρ𝗌𝗂𝗀𝗇𝖾𝖽 is deterministically bound to the delegation context:

ρ𝗌𝗂𝗀𝗇𝖾𝖽=𝖯𝗈𝗌𝖾𝗂𝖽𝗈𝗇(𝖼𝗆𝗑_𝟣,𝖼𝗆𝗑_𝟤,𝖼𝗆𝗑_𝟥,𝖼𝗆𝗑_𝟦,𝖼𝗆𝗑_𝟧,𝗏𝖺𝗇,𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽)

This makes the spend authorization signature non-replayable and scoped to the exact delegation context.

Spend authority. The circuit MUST enforce that 𝗋𝗄=𝖲𝗉𝖾𝗇𝖽𝖠𝗎𝗍𝗁𝖲𝗂𝗀𝖮𝗋𝖼𝗁𝖺𝗋𝖽.𝖱𝖺𝗇𝖽𝗈𝗆𝗂𝗓𝖾𝖯𝗎𝖻𝗅𝗂𝖼(α,𝖺𝗄ℙ).

Diversified address integrity for signed note. The circuit MUST enforce that the signed note's address belongs to (𝖺𝗄,𝗇𝗄).

Output note commitment. The circuit MUST enforce that 𝖼𝗆𝗑_𝗇𝖾𝗐 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 𝖼𝗆𝗑_𝗇𝖾𝗐 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 3 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)]):

𝗏𝖺𝗇_𝖼𝗈𝗋𝖾=𝖯𝗈𝗌𝖾𝗂𝖽𝗈𝗇(𝖣𝖮𝖬𝖠𝖨𝖭_𝖵𝖠𝖭,𝗏𝗉𝗄_𝗀_𝖽,𝗏𝗉𝗄_𝗉𝗄_𝖽,𝗇𝗎𝗆_𝖻𝖺𝗅𝗅𝗈𝗍𝗌,𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽,𝖬𝖠𝖷_𝖯𝖱𝖮𝖯𝖮𝖲𝖠𝖫_𝖠𝖴𝖳𝖧𝖮𝖱𝖨𝖳𝖸)

𝗏𝖺𝗇=𝖯𝗈𝗌𝖾𝗂𝖽𝗈𝗇(𝗏𝖺𝗇_𝖼𝗈𝗋𝖾,𝗀𝗈𝗏_𝖼𝗈𝗆𝗆_𝗋𝖺𝗇𝖽)

where 𝖬𝖠𝖷_𝖯𝖱𝖮𝖯𝖮𝖲𝖠𝖫_𝖠𝖴𝖳𝖧𝖮𝖱𝖨𝖳𝖸=216−1.

Ballot scaling. The circuit MUST enforce that 𝗇𝗎𝗆_𝖻𝖺𝗅𝗅𝗈𝗍𝗌=⌊∑vi/12,500,000⌋ with 𝗇𝗎𝗆_𝖻𝖺𝗅𝗅𝗈𝗍𝗌≥1, as defined in [Ballot Scaling].

Out-of-Circuit Verification

A verifier that receives a Delegation Proof π together with a spend authorization signature σ MUST perform the following checks:

  1. Verify π against the public inputs.
  2. Verify that 𝗌𝗂𝗀𝗁𝖺𝗌𝗁_𝖽𝖾𝗅 is exactly 32 bytes.
  3. Verify σ as a valid 𝖲𝗉𝖾𝗇𝖽𝖠𝗎𝗍𝗁𝖲𝗂𝗀𝖮𝗋𝖼𝗁𝖺𝗋𝖽 signature on 𝗌𝗂𝗀𝗁𝖺𝗌𝗁_𝖽𝖾𝗅, under 𝗋𝗄.
  4. Verify that 𝗋𝗍𝖼𝗆 and 𝗋𝗍𝖾𝗑𝖼𝗅 correspond to the published pool snapshot for 𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽.
  5. Verify that no 𝗀𝗈𝗏_𝗇𝗎𝗅𝗅_𝗂 appears in the governance nullifier set. If any does, reject as a double-delegation.
  6. Verify that 𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽 matches an active round.
  7. Insert 𝗏𝖺𝗇 into the VCT.
  8. Add all 𝗀𝗈𝗏_𝗇𝗎𝗅𝗅_𝗂 to the governance nullifier set.

Delegation Sighash

The delegation sighash 𝗌𝗂𝗀𝗁𝖺𝗌𝗁_𝖽𝖾𝗅 is a client-provided 32-byte value included in the delegation message. For hardware wallet flows, the client computes it as the ZIP 244 14 shielded transaction sighash of a governance PCZT 6; the construction of this PCZT is specified in 3. For software wallets, the client signs the sighash directly without PCZT construction.

The vote chain verifies the 𝖲𝗉𝖾𝗇𝖽𝖠𝗎𝗍𝗁𝖲𝗂𝗀𝖮𝗋𝖼𝗁𝖺𝗋𝖽 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 𝗋𝗄 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 𝗋𝗄 without knowledge of the holder's spending key.

Delegation Message

A delegation transaction submitted to the vote chain MUST contain:

FieldTypeDescription
π_𝖽𝖾𝗅ProofThe Delegation Proof
σ_𝖽𝖾𝗅SignatureSpendAuthSig under 𝗋𝗄
𝗌𝗂𝗀𝗁𝖺𝗌𝗁_𝖽𝖾𝗅32 bytesClient-computed sighash (see [Delegation Sighash])
𝗌𝗂𝗀𝗇𝖾𝖽_𝗇𝗈𝗍𝖾_𝗇𝗎𝗅𝗅𝗂𝖿𝗂𝖾𝗋Pallas scalarDummy note nullifier
𝗋𝗄Pallas pointRandomized verification key
𝗋𝗍𝖼𝗆Pallas scalarNote commitment tree root
𝗋𝗍𝖾𝗑𝖼𝗅Pallas scalarNon-membership tree root
𝗏𝖺𝗇Pallas scalarVAN commitment
𝗀𝗈𝗏_𝗇𝗎𝗅𝗅_1…𝗀𝗈𝗏_𝗇𝗎𝗅𝗅_5Pallas scalar eachGovernance nullifiers
𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽Pallas scalarRound identifier
𝖼𝗆𝗑_𝗇𝖾𝗐Pallas scalarOutput 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 Ns 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:

  • 𝗏𝖺𝗇_𝗇𝗎𝗅𝗅𝗂𝖿𝗂𝖾𝗋⦂{0..qℙ−1} — nullifier of the old VAN (prevents double-voting).
  • 𝗋_𝗏𝗉𝗄⦂𝖲𝗉𝖾𝗇𝖽𝖠𝗎𝗍𝗁𝖲𝗂𝗀𝖮𝗋𝖼𝗁𝖺𝗋𝖽.𝖯𝗎𝖻𝗅𝗂𝖼 — randomized voting public key.
  • 𝗏𝖺𝗇_𝗇𝖾𝗐⦂{0..qℙ−1} — the new VAN commitment with decremented proposal authority.
  • 𝗏𝖼⦂{0..qℙ−1} — the vote commitment.
  • 𝗋𝗍𝗏𝖼𝗍⦂{0..qℙ−1} — root of the Vote Commitment Tree.
  • 𝖺𝗇𝖼𝗁𝗈𝗋_𝗁𝖾𝗂𝗀𝗁𝗍⦂ℕ — VCT snapshot height.
  • 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽⦂{1…15} — which proposal.
  • 𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽⦂{0..qℙ−1}
  • 𝖾𝖺_𝗉𝗄⦂ℙ∗ — election authority public key (x and y coordinates).

Auxiliary Inputs

The prover knows:

  • 𝗏𝗈𝗍𝖾_𝖽𝖾𝖼𝗂𝗌𝗂𝗈𝗇⦂{0..qℙ−1} — the voter's choice.
  • 𝗏𝗌𝗄.𝖺𝗄⦂ℙ∗ — voting spend authorization validating key.
  • 𝗏𝗌𝗄.𝗇𝗄⦂{0..qℙ−1} — nullifier deriving key from the governance hotkey's full viewing key.
  • 𝗋𝗂𝗏𝗄_𝗏⦂𝖢𝗈𝗆𝗆𝗂𝗍𝗂𝗏𝗄.𝖳𝗋𝖺𝗉𝖽𝗈𝗈𝗋 — CommitIvk randomness for the voting key.
  • αv — spend authorization randomizer for the voting hotkey.
  • 𝗏𝗉𝗄_𝗀_𝖽⦂ℙ∗ — diversified base point from the VAN (full point; 𝖤𝗑𝗍𝗋𝖺𝖼𝗍_ℙ applied for VAN integrity hash).
  • 𝗏𝗉𝗄_𝗉𝗄_𝖽⦂ℙ∗ — diversified transmission key point from the VAN (full point; 𝖤𝗑𝗍𝗋𝖺𝖼𝗍_ℙ applied for VAN integrity hash).
  • 𝗇𝗎𝗆_𝖻𝖺𝗅𝗅𝗈𝗍𝗌 — total voting weight in ballots.
  • 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝖺𝗎𝗍𝗁𝗈𝗋𝗂𝗍𝗒_𝗈𝗅𝖽, 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝖺𝗎𝗍𝗁𝗈𝗋𝗂𝗍𝗒_𝗇𝖾𝗐 — old and new bitmasks.
  • 𝗀𝗈𝗏_𝖼𝗈𝗆𝗆_𝗋𝖺𝗇𝖽 — VAN commitment randomness (shared between old and new VAN).
  • 𝗉𝖺𝗍𝗁𝗏𝖼𝗍,𝗉𝗈𝗌𝗏𝖼𝗍 — Merkle proof for the old VAN in the VCT.
  • 𝗏𝖺𝗇𝗈𝗅𝖽 — old VAN commitment.
  • 𝗏_𝟢,…,𝗏Ns−1 — plaintext share values.
  • r0,…,rNs−1 — El Gamal encryption randomness per share.
  • 𝖻𝗅𝗂𝗇𝖽_𝟢,…,𝖻𝗅𝗂𝗇𝖽Ns−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: (𝗉𝖺𝗍𝗁𝗏𝖼𝗍,𝗉𝗈𝗌𝗏𝖼𝗍) MUST be a valid Merkle path of depth 𝖬𝖾𝗋𝗄𝗅𝖾𝖣𝖾𝗉𝗍𝗁𝗏𝖼𝗍 from 𝗏𝖺𝗇𝗈𝗅𝖽 to the anchor 𝗋𝗍𝗏𝖼𝗍, 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 𝗏𝗉𝗄 values in the Poseidon input are x-coordinates, i.e., 𝖤𝗑𝗍𝗋𝖺𝖼𝗍_ℙ of the corresponding full points:

𝗏𝖺𝗇_𝖼𝗈𝗋𝖾_𝗈𝗅𝖽=𝖯𝗈𝗌𝖾𝗂𝖽𝗈𝗇(𝖣𝖮𝖬𝖠𝖨𝖭_𝖵𝖠𝖭,𝗏𝗉𝗄_𝗀_𝖽,𝗏𝗉𝗄_𝗉𝗄_𝖽,𝗇𝗎𝗆_𝖻𝖺𝗅𝗅𝗈𝗍𝗌,𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽,𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝖺𝗎𝗍𝗁𝗈𝗋𝗂𝗍𝗒_𝗈𝗅𝖽)

𝗏𝖺𝗇_𝗈𝗅𝖽=𝖯𝗈𝗌𝖾𝗂𝖽𝗈𝗇(𝗏𝖺𝗇_𝖼𝗈𝗋𝖾_𝗈𝗅𝖽,𝗀𝗈𝗏_𝖼𝗈𝗆𝗆_𝗋𝖺𝗇𝖽)

Condition 3: Diversified address integrity. The circuit MUST enforce that the VAN's address belongs to the voting key:

𝗏𝗉𝗄_𝗉𝗄_𝖽=[𝗂𝗏𝗄_𝗏]𝗏𝗉𝗄_𝗀_𝖽

where 𝗂𝗏𝗄_𝗏=𝖢𝗈𝗆𝗆𝗂𝗍𝖨𝗏𝗄𝗋𝗂𝗏𝗄_𝗏(𝖤𝗑𝗍𝗋𝖺𝖼𝗍ℙ(𝗏𝗌𝗄.𝖺𝗄),𝗏𝗌𝗄.𝗇𝗄) 15.

Condition 4: Spend authority. The circuit MUST enforce that the randomized voting public key is a valid rerandomization:

𝗋_𝗏𝗉𝗄=𝗏𝗌𝗄.𝖺𝗄+[αv]G

Condition 5: VAN nullifier. The circuit MUST enforce that the public 𝗏𝖺𝗇_𝗇𝗎𝗅𝗅𝗂𝖿𝗂𝖾𝗋 is correctly derived:

𝗏𝖺𝗇_𝗇𝗎𝗅𝗅𝗂𝖿𝗂𝖾𝗋=𝖯𝗈𝗌𝖾𝗂𝖽𝗈𝗇(𝗏𝗌𝗄.𝗇𝗄,𝗍𝖺𝗀𝗏𝖺𝗇,𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽,𝗏𝖺𝗇𝗈𝗅𝖽)

where 𝗍𝖺𝗀𝗏𝖺𝗇 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 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽 is cleared in the authority bitmask:

  • 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝖺𝗎𝗍𝗁𝗈𝗋𝗂𝗍𝗒_𝗈𝗅𝖽 MUST be decomposed into 16 boolean wires b0,…,b15 that recompose to the original value.
  • The bit at position 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽 MUST be 1 (the voter has authority for this proposal).
  • 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝖺𝗎𝗍𝗁𝗈𝗋𝗂𝗍𝗒_𝗇𝖾𝗐 MUST be the recomposition with bit 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽 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)]):

𝗏𝖺𝗇_𝖼𝗈𝗋𝖾_𝗇𝖾𝗐=𝖯𝗈𝗌𝖾𝗂𝖽𝗈𝗇(𝖣𝖮𝖬𝖠𝖨𝖭_𝖵𝖠𝖭,𝗏𝗉𝗄_𝗀_𝖽,𝗏𝗉𝗄_𝗉𝗄_𝖽,𝗇𝗎𝗆_𝖻𝖺𝗅𝗅𝗈𝗍𝗌,𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽,𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝖺𝗎𝗍𝗁𝗈𝗋𝗂𝗍𝗒_𝗇𝖾𝗐)

𝗏𝖺𝗇_𝗇𝖾𝗐=𝖯𝗈𝗌𝖾𝗂𝖽𝗈𝗇(𝗏𝖺𝗇_𝖼𝗈𝗋𝖾_𝗇𝖾𝗐,𝗀𝗈𝗏_𝖼𝗈𝗆𝗆_𝗋𝖺𝗇𝖽)

The new VAN MUST reuse the old VAN's diversified address and commitment randomness; only 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝖺𝗎𝗍𝗁𝗈𝗋𝗂𝗍𝗒 changes.

Vote Commitment Construction

Condition 8: Shares sum correctness. The circuit MUST enforce:

∑i=0Ns−1𝗏_𝗂=𝗇𝗎𝗆_𝖻𝖺𝗅𝗅𝗈𝗍𝗌

Condition 9: Shares range check. The circuit MUST enforce that each share is bounded:

0≤𝗏_𝗂<230for each i∈{0…Ns−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 7).

Condition 10: Shares hash integrity. The circuit MUST enforce that the blinded share commitments and their aggregate hash are correctly computed:

𝗌𝗁𝖺𝗋𝖾_𝖼𝗈𝗆𝗆_𝗂=𝖯𝗈𝗌𝖾𝗂𝖽𝗈𝗇(𝖻𝗅𝗂𝗇𝖽_𝗂,C1,i,x,C2,i,x,C1,i,y,C2,i,y)for each i

𝗌𝗁𝖺𝗋𝖾𝗌_𝗁𝖺𝗌𝗁=𝖯𝗈𝗌𝖾𝗂𝖽𝗈𝗇(𝗌𝗁𝖺𝗋𝖾_𝖼𝗈𝗆𝗆_𝟢,…,𝗌𝗁𝖺𝗋𝖾_𝖼𝗈𝗆𝗆Ns−1)

Condition 11: El Gamal encryption integrity. The circuit MUST enforce that each ciphertext is a valid encryption of its share under 𝖾𝖺_𝗉𝗄:

C1,i=[ri]G C2,i=[𝗏_𝗂]G+[ri]𝖾𝖺_𝗉𝗄

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:

𝗏𝖼=𝖯𝗈𝗌𝖾𝗂𝖽𝗈𝗇(𝖣𝖮𝖬𝖠𝖨𝖭_𝖵𝖢,𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽,𝗌𝗁𝖺𝗋𝖾𝗌_𝗁𝖺𝗌𝗁,𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽,𝗏𝗈𝗍𝖾_𝖽𝖾𝖼𝗂𝗌𝗂𝗈𝗇)

Out-of-Circuit Verification

A verifier that receives a Vote Proof π together with a vote spend authorization signature σ MUST perform the following checks:

  1. Verify π against the public inputs.
  2. Compute the vote sighash from the message fields (see [Vote Sighash]).
  3. Verify σ as a valid 𝖲𝗉𝖾𝗇𝖽𝖠𝗎𝗍𝗁𝖲𝗂𝗀𝖮𝗋𝖼𝗁𝖺𝗋𝖽 signature on the vote sighash, under 𝗋_𝗏𝗉𝗄.
  4. Verify that 𝗏𝖺𝗇_𝗇𝗎𝗅𝗅𝗂𝖿𝗂𝖾𝗋 does not appear in the VAN nullifier set. If it does, reject as double-voting.
  5. Verify that 𝖺𝗇𝖼𝗁𝗈𝗋_𝗁𝖾𝗂𝗀𝗁𝗍 refers to a VCT state within the current voting round and that 𝗋𝗍𝗏𝖼𝗍 matches the VCT root at that height.
  6. Verify that 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽 is valid for the current round and within the voting window.
  7. Verify that 𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽 matches an active round.
  8. Verify that 𝖾𝖺_𝗉𝗄 matches the published election authority public key for this round.
  9. Insert 𝗏𝖺𝗇𝗇𝖾𝗐 and 𝗏𝖼 into the VCT.
  10. Add 𝗏𝖺𝗇_𝗇𝗎𝗅𝗅𝗂𝖿𝗂𝖾𝗋 to the VAN nullifier set.

Note: 𝗏𝗈𝗍𝖾_𝖽𝖾𝖼𝗂𝗌𝗂𝗈𝗇 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 𝗏𝗈𝗍𝖾_𝖽𝖾𝖼𝗂𝗌𝗂𝗈𝗇 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 𝖡𝖫𝖠𝖪𝖤𝟤𝖻-𝟤𝟧𝟨(𝗏𝗈𝗍𝖾_𝗉𝖺𝗒𝗅𝗈𝖺𝖽) using an unkeyed BLAKE2b-256 hash (no personalization parameter). 𝗏𝗈𝗍𝖾_𝗉𝖺𝗒𝗅𝗈𝖺𝖽 is the concatenation of a domain prefix followed by the message fields, each zero-padded to 32 bytes:

ComponentEncodingSize
"SVOTE_CAST_VOTE_SIGHASH_V0"raw ASCII bytes26
𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽field element, zero-padded to 3232
𝗋_𝗏𝗉𝗄compressed point, zero-padded to 3232
𝗏𝖺𝗇_𝗇𝗎𝗅𝗅𝗂𝖿𝗂𝖾𝗋field element, zero-padded to 3232
𝗏𝖺𝗇_𝗇𝖾𝗐field element, zero-padded to 3232
𝗏𝖼field element, zero-padded to 3232
𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽4-byte LE integer, zero-padded to 3232
𝖺𝗇𝖼𝗁𝗈𝗋_𝗁𝖾𝗂𝗀𝗁𝗍8-byte LE integer, zero-padded to 3232

Total payload: 250 bytes. Pallas field elements and compressed points use their canonical little-endian encoding 13, 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 αv) before submitting the vote message.

Vote Message

A vote transaction submitted to the vote chain MUST contain:

FieldTypeDescription
πvoteProofThe Vote Proof
σvoteSignatureSpendAuthSig under 𝗋𝗏𝗉𝗄
𝗏𝖺𝗇_𝗇𝗎𝗅𝗅𝗂𝖿𝗂𝖾𝗋Pallas scalarOld VAN nullifier
𝗋𝗏𝗉𝗄Pallas pointRandomized voting public key
𝗏𝖺𝗇𝗇𝖾𝗐Pallas scalarNew VAN commitment
𝗏𝖼Pallas scalarVote commitment
𝗋𝗍𝗏𝖼𝗍Pallas scalarVCT root
𝖺𝗇𝖼𝗁𝗈𝗋_𝗁𝖾𝗂𝗀𝗁𝗍integerVCT anchor height
𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽{1…15}Proposal identifier
𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽Pallas scalarRound identifier
𝖾𝖺𝗉𝗄Pallas pointEA 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:

  • 𝗌𝗁𝖺𝗋𝖾_𝗇𝗎𝗅𝗅𝗂𝖿𝗂𝖾𝗋⦂{0..qℙ−1} — prevents double-counting.
  • C1,x,C1,y,C2,x,C2,y⦂{0..qℙ−1} — the x- and y-coordinates of the El Gamal ciphertext points (C1,C2) for this share.
  • 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽⦂{1…15} — which proposal.
  • 𝗏𝗈𝗍𝖾_𝖽𝖾𝖼𝗂𝗌𝗂𝗈𝗇⦂{0..qℙ−1} — the voter's choice.
  • 𝗋𝗍𝗏𝖼𝗍⦂{0..qℙ−1} — root of the Vote Commitment Tree.
  • 𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽⦂{0..qℙ−1}

Auxiliary Inputs

The prover (submission server) knows:

  • 𝗏𝖼⦂{0..qℙ−1} — the vote commitment being opened (hidden from the verifier).
  • 𝗉𝖺𝗍𝗁𝗏𝖼𝗍,𝗉𝗈𝗌𝗏𝖼𝗍 — Merkle proof for the VC in the VCT.
  • 𝗌𝗁𝖺𝗋𝖾𝗌_𝗁𝖺𝗌𝗁⦂{0..qℙ−1}
  • 𝗌𝗁𝖺𝗋𝖾_𝗂𝗇𝖽𝖾𝗑∈{0,1,…,Ns−1} — which share is being revealed.
  • 𝗌𝗁𝖺𝗋𝖾_𝖼𝗈𝗆𝗆_0…𝗌𝗁𝖺𝗋𝖾_𝖼𝗈𝗆𝗆_Ns−1 — all Ns blinded share commitments (to recompute 𝗌𝗁𝖺𝗋𝖾𝗌_𝗁𝖺𝗌𝗁).
  • 𝖻𝗅𝗂𝗇𝖽 — the blind factor for the revealed share (at position 𝗌𝗁𝖺𝗋𝖾_𝗂𝗇𝖽𝖾𝗑).

Conditions

Vote Commitment Membership

Condition 1: Merkle tree membership. The circuit MUST enforce that the VC exists in the VCT: (𝗉𝖺𝗍𝗁𝗏𝖼𝗍,𝗉𝗈𝗌𝗏𝖼𝗍) MUST be a valid Merkle path from 𝗏𝖼 to 𝗋𝗍𝗏𝖼𝗍, 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:

𝗏𝖼=𝖯𝗈𝗌𝖾𝗂𝖽𝗈𝗇(𝖣𝖮𝖬𝖠𝖨𝖭_𝖵𝖢,𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽,𝗌𝗁𝖺𝗋𝖾𝗌_𝗁𝖺𝗌𝗁,𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽,𝗏𝗈𝗍𝖾_𝖽𝖾𝖼𝗂𝗌𝗂𝗈𝗇)

This binds the public 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽 and 𝗏𝗈𝗍𝖾_𝖽𝖾𝖼𝗂𝗌𝗂𝗈𝗇 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 𝗌𝗁𝖺𝗋𝖾𝗌_𝗁𝖺𝗌𝗁 is recomputed from the witness share commitments:

𝗌𝗁𝖺𝗋𝖾𝗌_𝗁𝖺𝗌𝗁=𝖯𝗈𝗌𝖾𝗂𝖽𝗈𝗇(𝗌𝗁𝖺𝗋𝖾_𝖼𝗈𝗆𝗆_𝟢,…,𝗌𝗁𝖺𝗋𝖾_𝖼𝗈𝗆𝗆Ns−1)

The recomputed 𝗌𝗁𝖺𝗋𝖾𝗌_𝗁𝖺𝗌𝗁 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 C1,x,C2,x,C1,y,C2,y and the witness blind factor matches the share commitment at position 𝗌𝗁𝖺𝗋𝖾_𝗂𝗇𝖽𝖾𝗑:

𝖯𝗈𝗌𝖾𝗂𝖽𝗈𝗇(𝖻𝗅𝗂𝗇𝖽𝗌𝗁𝖺𝗋𝖾_𝗂𝗇𝖽𝖾𝗑,C1,x,C2,x,C1,y,C2,y)=𝗌𝗁𝖺𝗋𝖾_𝖼𝗈𝗆𝗆𝗌[𝗌𝗁𝖺𝗋𝖾_𝗂𝗇𝖽𝖾𝗑]

The circuit MUST encode 𝗌𝗁𝖺𝗋𝖾_𝗂𝗇𝖽𝖾𝗑 as a one-hot selector vector over Ns positions. The mux MUST extract the corresponding 𝗌𝗁𝖺𝗋𝖾_𝖼𝗈𝗆𝗆 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 Ns−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 𝗌𝗁𝖺𝗋𝖾_𝗇𝗎𝗅𝗅𝗂𝖿𝗂𝖾𝗋 is correctly derived:

𝗌𝗁𝖺𝗋𝖾_𝗇𝗎𝗅𝗅𝗂𝖿𝗂𝖾𝗋=𝖯𝗈𝗌𝖾𝗂𝖽𝗈𝗇(𝗍𝖺𝗀𝗌𝗁𝖺𝗋𝖾,𝗏𝖼,𝗌𝗁𝖺𝗋𝖾_𝗂𝗇𝖽𝖾𝗑,𝖻𝗅𝗂𝗇𝖽)

where 𝗍𝖺𝗀𝗌𝗁𝖺𝗋𝖾 is the field-element encoding of "share spend" and 𝖻𝗅𝗂𝗇𝖽 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 π MUST perform the following checks:

  1. Verify π against the public inputs.
  2. Verify that 𝗌𝗁𝖺𝗋𝖾_𝗇𝗎𝗅𝗅𝗂𝖿𝗂𝖾𝗋 does not appear in the share nullifier set. If it does, reject as double-counting.
  3. Verify that 𝗋𝗍𝗏𝖼𝗍 matches a published VCT root.
  4. Verify that 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽 is valid for the current round.
  5. Verify that 𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽 matches an active round.
  6. Verify that 𝗏𝗈𝗍𝖾_𝖽𝖾𝖼𝗂𝗌𝗂𝗈𝗇 is a valid option for the proposal identified by 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽 in this round.
  7. Add 𝗌𝗁𝖺𝗋𝖾_𝗇𝗎𝗅𝗅𝗂𝖿𝗂𝖾𝗋 to the share nullifier set.
  8. Accumulate 𝖾𝗇𝖼_𝗌𝗁𝖺𝗋𝖾 into the aggregate ciphertext for (𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽,𝗏𝗈𝗍𝖾_𝖽𝖾𝖼𝗂𝗌𝗂𝗈𝗇):

𝖺𝗀𝗀[𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽][𝗏𝗈𝗍𝖾_𝖽𝖾𝖼𝗂𝗌𝗂𝗈𝗇]+=𝖾𝗇𝖼_𝗌𝗁𝖺𝗋𝖾

where + denotes component-wise Pallas point addition.

Share Reveal Message

A share reveal transaction submitted to the vote chain MUST contain:

FieldTypeDescription
πrevealProofThe Vote Reveal Proof
𝗌𝗁𝖺𝗋𝖾_𝗇𝗎𝗅𝗅𝗂𝖿𝗂𝖾𝗋Pallas scalarShare nullifier
𝖾𝗇𝖼_𝗌𝗁𝖺𝗋𝖾(C1,C2)El Gamal ciphertext (two Pallas points)
𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽{1…15}Proposal identifier
𝗏𝗈𝗍𝖾_𝖽𝖾𝖼𝗂𝗌𝗂𝗈𝗇Pallas scalarVote decision
𝗋𝗍𝗏𝖼𝗍Pallas scalarVCT root
𝗏𝗈𝗍𝗂𝗇𝗀_𝗋𝗈𝗎𝗇𝖽_𝗂𝖽Pallas scalarRound 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:

FieldDescription
𝗏𝖼The vote commitment
VCT positionPosition of 𝗏𝖼 in the VCT
𝗌𝗁𝖺𝗋𝖾𝗌_𝗁𝖺𝗌𝗁Hash of all blinded share commitments
𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽Proposal identifier
𝗏𝗈𝗍𝖾_𝖽𝖾𝖼𝗂𝗌𝗂𝗈𝗇Vote decision
𝗌𝗁𝖺𝗋𝖾_𝗂𝗇𝖽𝖾𝗑Which share to reveal (0-indexed)
𝖾𝗇𝖼_𝗌𝗁𝖺𝗋𝖾El Gamal ciphertext (C1,C2) for this share
𝖻𝗅𝗂𝗇𝖽Blind factor for this share
𝗌𝗁𝖺𝗋𝖾_𝖼𝗈𝗆𝗆_0…𝗌𝗁𝖺𝗋𝖾_𝖼𝗈𝗆𝗆_Ns−1All Ns blinded share commitments
𝗌𝗎𝖻𝗆𝗂𝗍_𝖺𝗍Unix timestamp (seconds) for when the server should submit the share reveal transaction. 0 means immediate (last-moment mode). See 4

The server receives only the ciphertext and blind factor for the single share it is responsible for revealing. The remaining Ns−1 shares are sent to other servers, each of which receives only its own share's raw data. To recompute 𝗌𝗁𝖺𝗋𝖾𝗌_𝗁𝖺𝗌𝗁 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 4.

Tally

After the voting window closes, the vote chain MUST decrypt the per- (𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽,𝗏𝗈𝗍𝖾_𝖽𝖾𝖼𝗂𝗌𝗂𝗈𝗇) aggregate ciphertexts using the threshold decryption procedure specified in 7.

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-(𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽,𝗏𝗈𝗍𝖾_𝖽𝖾𝖼𝗂𝗌𝗂𝗈𝗇) 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 3: 𝖯𝟣𝟤𝟪𝖯𝗈𝗐𝟧𝖳𝟥 over 𝔽qℙ (the Pallas base field), with S-box x5, width t=3, rate r=2, targeting 128-bit security, using the standard parameter generation procedure from 16.

Hashes with L inputs use 𝖢𝗈𝗇𝗌𝗍𝖺𝗇𝗍𝖫𝖾𝗇𝗀𝗍𝗁⟨L⟩ mode (absorbing L field elements with length padding), absorbing two elements per permutation.

The following table lists every Poseidon call site in this protocol:

HashInputs (L)ModePermutations
Voting round identifier8𝖢𝗈𝗇𝗌𝗍𝖺𝗇𝗍𝖫𝖾𝗇𝗀𝗍𝗁⟨8⟩4
VAN core6𝖢𝗈𝗇𝗌𝗍𝖺𝗇𝗍𝖫𝖾𝗇𝗀𝗍𝗁⟨6⟩3
VAN blinding2𝖢𝗈𝗇𝗌𝗍𝖺𝗇𝗍𝖫𝖾𝗇𝗀𝗍𝗁⟨2⟩1
VAN nullifier4𝖢𝗈𝗇𝗌𝗍𝖺𝗇𝗍𝖫𝖾𝗇𝗀𝗍𝗁⟨4⟩2
Vote commitment5𝖢𝗈𝗇𝗌𝗍𝖺𝗇𝗍𝖫𝖾𝗇𝗀𝗍𝗁⟨5⟩3
Blinded share commitment5𝖢𝗈𝗇𝗌𝗍𝖺𝗇𝗍𝖫𝖾𝗇𝗀𝗍𝗁⟨5⟩3
Shares hash16𝖢𝗈𝗇𝗌𝗍𝖺𝗇𝗍𝖫𝖾𝗇𝗀𝗍𝗁⟨16⟩8
Share nullifier4𝖢𝗈𝗇𝗌𝗍𝖺𝗇𝗍𝖫𝖾𝗇𝗀𝗍𝗁⟨4⟩2
VCT internal node2𝖢𝗈𝗇𝗌𝗍𝖺𝗇𝗍𝖫𝖾𝗇𝗀𝗍𝗁⟨2⟩1
Governance nullifier4𝖢𝗈𝗇𝗌𝗍𝖺𝗇𝗍𝖫𝖾𝗇𝗀𝗍𝗁⟨4⟩2
Rho binding7𝖢𝗈𝗇𝗌𝗍𝖺𝗇𝗍𝖫𝖾𝗇𝗀𝗍𝗁⟨7⟩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:

𝗍𝖺𝗀=∑j=0ℓ−1bj⋅256j

where b0,…,bℓ−1 are the ASCII byte values of the string and ℓ is its length. All tag strings in this protocol are shorter than 32 bytes, so the resulting integer is less than 2256 and fits in 𝔽qℙ 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 17 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 18 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 3.

Why Ballot Scaling

Expressing vote values in ballots (⌊zatoshi/12,500,000⌋) 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 7 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: 𝗇𝗎𝗆_𝖻𝖺𝗅𝗅𝗈𝗍𝗌 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 𝖲𝗉𝖾𝗇𝖽𝖠𝗎𝗍𝗁𝖲𝗂𝗀𝖮𝗋𝖼𝗁𝖺𝗋𝖽 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 (ri, 𝖻𝗅𝗂𝗇𝖽_𝗂, α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 (𝗏𝗌𝗄, 𝗏𝗌𝗄.𝗇𝗄, 𝗋𝗂𝗏𝗄_𝗏, and all per-vote ri and 𝖻𝗅𝗂𝗇𝖽_𝗂 values) independently, and any storage failure would be unrecoverable.

Why VAN Nullifier Domain Separation

The VAN nullifier uses 𝗏𝗌𝗄.𝗇𝗄 (the governance hotkey's nullifier deriving key) as the Poseidon key, while the governance nullifier uses 𝗇𝗄 (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), 𝗏𝗌𝗄.𝗇𝗄=𝗇𝗄 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 𝖲𝗉𝖾𝗇𝖽𝖠𝗎𝗍𝗁𝖲𝗂𝗀𝖮𝗋𝖼𝗁𝖺𝗋𝖽 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 3.

Why Ns Shares Per Vote

Splitting a vote into Ns 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 Ns proofs before the deadline. See 4 for the last-moment buffer definition and timing details.

Why Blinded Share Commitments

Each share commitment includes a random blind factor: 𝗌𝗁𝖺𝗋𝖾_𝖼𝗈𝗆𝗆_𝗂=𝖯𝗈𝗌𝖾𝗂𝖽𝗈𝗇(𝖻𝗅𝗂𝗇𝖽_𝗂,C1,i,x,C2,i,x,C1,i,y,C2,i,y). Without blinding, an observer could compute 𝖯𝗈𝗌𝖾𝗂𝖽𝗈𝗇(C1,i,x,C2,i,x,C1,i,y,C2,i,y) for each on-chain ciphertext and compare against the 𝗌𝗁𝖺𝗋𝖾𝗌_𝗁𝖺𝗌𝗁 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 C1 and C2 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 𝖤𝗇𝖼(−v) instead of 𝖤𝗇𝖼(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 𝗌𝗁𝖺𝗋𝖾_𝖼𝗈𝗆𝗆, which cascades through 𝗌𝗁𝖺𝗋𝖾𝗌_𝗁𝖺𝗌𝗁→𝗏𝖼→ 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 Ns 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 𝗌𝗁𝖺𝗋𝖾𝗌_𝗁𝖺𝗌𝗁 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 (𝗏𝗉𝗄_𝗀_𝖽, 𝗏𝗉𝗄_𝗉𝗄_𝖽) and commitment randomness (𝗀𝗈𝗏_𝖼𝗈𝗆𝗆_𝗋𝖺𝗇𝖽). Only 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝖺𝗎𝗍𝗁𝗈𝗋𝗂𝗍𝗒 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 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝖺𝗎𝗍𝗁𝗈𝗋𝗂𝗍𝗒 bitmask is 16 bits wide, but 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽 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 (𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽,2𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽) 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, 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽=0 cannot be used as a valid proposal identifier: a prover could trivially satisfy the lookup with 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽=0 even when no authority check is intended. An additional non-zero gate (𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽⋅𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽−1=1) provides defense-in-depth by rejecting 𝗉𝗋𝗈𝗉𝗈𝗌𝖺𝗅_𝗂𝖽=0 on active rows.

Why Threshold Secret Sharing

See 7 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 𝗇𝗎𝗆_𝖻𝖺𝗅𝗅𝗈𝗍𝗌 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 7 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 Ns 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 232 (~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 224 (~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 (𝖣𝖮𝖬𝖠𝖨𝖭_𝖵𝖠𝖭=0, 𝖣𝖮𝖬𝖠𝖨𝖭_𝖵𝖢=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

  • 19 — Halo 2 circuits for the Delegation Proof, Vote Proof, and Vote Reveal Proof.
  • 20 — Cosmos SDK vote chain implementing the VCT, nullifier sets, encrypted share accumulator, ceremony, tally, and submission server.
  • 21 — PIR server and client for privately retrieving nullifier non-membership proofs.
  • 22 — 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 3.
  • Partial delegation (a VAN-to-VAN delegation proof that consumes one VAN and produces two with subdivided 𝗇𝗎𝗆_𝖻𝖺𝗅𝗅𝗈𝗍𝗌) 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 7.
  • Open issues related to the submission server (share decomposition strategy, client confirmation via PIR, balance amendment, decision encryption) are tracked in 4.
  • Open issues related to the balance proof are tracked in 3.

References

Footnotes

  1. 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" ↩

  2. Zcash Protocol Specification, Version 2025.6.3 [NU6.1] or later ↩

  3. Orchard Proof-of-Balance ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16

  4. Vote Share Submission Server ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8

  5. Private Information Retrieval for Nullifier Exclusion Proofs ↩ ↩2

  6. zcash/zips issue #693: Standardize a protocol for creating shielded transactions offline (PCZT) ↩ ↩2

  7. Election Authority Key Ceremony ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9

  8. Zcash Shielded Coinholder Voting ↩

  9. Zcash Protocol Specification, Version 2025.6.3 [NU6.1]. Section 5.4.7.1: Spend Authorization Signature (Orchard) ↩

  10. Zcash Protocol Specification, Version 2025.6.3 [NU6.1]. Section 3.8: Note Commitment Trees ↩

  11. M. Palatinus, P. Rusnak, A. Voisine, and S. Bowe, "BIP 39: Mnemonic code for generating deterministic keys", 2013 ↩

  12. Zcash Protocol Specification, Version 2025.6.3 [NU6.1]. Section 4.2.3: Orchard Key Components ↩

  13. Zcash Protocol Specification, Version 2025.6.3 [NU6.1]. Section 5.4.9.6: Pallas and Vesta ↩ ↩2

  14. ZIP 244: Transaction Identifier and Signature Validation for v5 Transactions ↩

  15. Zcash Protocol Specification, Version 2025.6.3 [NU6.1]. Section 5.4.9.4: CommitIvk ↩

  16. Poseidon: A New Hash Function for Zero-Knowledge Proof Systems ↩

  17. Zcash Protocol Specification, Version 2025.6.3 [NU6.1]. Section 5.4.1.9: Sinsemilla Hash Function ↩

  18. S. Bowe, J. Grigg, and D. Hopwood, "Recursive Proof Composition without a Trusted Setup", 2019 ↩

  19. valargroup/voting-circuits: Halo 2 ZKP circuits for shielded voting ↩

  20. valargroup/vote-sdk: Cosmos SDK vote chain for shielded voting ↩

  21. valargroup/vote-nullifier-pir: PIR system for nullifier non-membership proofs ↩

  22. valargroup/librustvoting: Client-side voting library for proof generation and vote construction ↩

Changes

zips/draft-valargroup-shielded-voting.md +1722 −0
@@ -0,0 +1,1722 @@ + ZIP: Unassigned + Title: Shielded Voting Protocol + Owners: Dev Ojha <dojha@berkeley.edu> + Roman Akhtariev <ackhtariev@gmail.com> + Adam Tucker <adamleetucker@outlook.com> + Greg Nagy <greg@dhamma.works> + Credits: Daira-Emma Hopwood + Jack Grigg + Status: Draft + Category: Standards + Created: 2026-03-04 + License: MIT + Pull-Request: <https://github.com/zcash/zips/pull/1200> + + +# 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)