From 84368f5798b6c608070de3a304e5f0332ea1242f Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 4 Mar 2026 19:17:16 -0300 Subject: [PATCH 01/56] initial commit; formatted Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 1214 ++++++++++++++++++++++ 1 file changed, 1214 insertions(+) create mode 100644 zips/draft-valargroup-shielded-voting.md diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md new file mode 100644 index 000000000..a276b17f0 --- /dev/null +++ b/zips/draft-valargroup-shielded-voting.md @@ -0,0 +1,1214 @@ + ZIP: Unassigned + Title: Shielded Voting Protocol + Owners: Dev Ojha + Roman Akhtariev + Adam Tucker + Greg Nagy + Credits: Daira-Emma Hopwood + Jack Grigg + Status: Draft + Category: Informational + Created: 2026-03-04 + License: MIT + Pull-Request: + + +# Terminology + +The key words "MUST", "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 equal to + $\lfloor v / 12{,}500{,}000 \rfloor$ where $v$ is a balance in + zatoshi. One ballot corresponds to 0.125 ZEC. + +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. + +Vote share + +: One of $N_s$ encrypted portions of a voter's ballot count within a + Vote Commitment. Each share is an El Gamal ciphertext under the + election authority's public key. + +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 entity (or set of validators) that holds the El Gamal secret key + $\mathsf{ea}\_\mathsf{sk}$ for a voting round and is responsible for decrypting + the aggregate tally after the voting window closes. + +Submission server + +: An untrusted server to which a voter delegates the construction and + submission of Vote Reveal Proofs. The server learns encrypted shares + and vote decisions but cannot decrypt share amounts or link shares to + voter identities. + +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. The election authority +decrypts the aggregate totals and publishes results with a +Chaum–Pedersen proof of correct decryption. 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 (ZKP #3) 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.** The election authority holds $\mathsf{ea}\_\mathsf{sk}$ and +can in principle decrypt individual share ciphertexts. Privacy against +the EA relies on vote splitting: the EA sees encrypted shares but cannot +link them to specific voters or vote commitments. Submission servers +learn the encrypted share ciphertext, proposal identifier, and vote +decision for each share they submit, 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 mitigates this risk. + +**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. +- Double-delegation of the same Orchard note within a voting round is + detectable via deterministic governance nullifiers. +- Double-voting with the same Vote Authority Note is detectable via + deterministic VAN nullifiers. +- Double-counting of the same vote share is detectable via deterministic + share nullifiers. +- 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 and verify the election authority's + decryption proof. +- The protocol supports up to 16 proposals per voting round. +- The protocol supports delegation of voting authority to a third-party + hotkey. + + +# Non-requirements + +- The consensus mechanism and operational parameters of the vote chain + are out of scope for this ZIP. +- The election authority key ceremony (generation and distribution of + $\mathsf{ea}\_\mathsf{sk}$) 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 expected to + be specified in a separate ZIP. +- Threshold decryption (splitting $\mathsf{ea}\_\mathsf{sk}$ among multiple + parties) is out of scope. +- 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]. + + +# Specification + +## Protocol Overview + +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. Each payload +contains the data necessary for the server to construct a Vote Reveal +Proof. + +**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 a randomized +delay. The chain accumulates the revealed El Gamal ciphertexts +homomorphically. + +**Phase 5 — Tally.** After the voting window closes, the election +authority decrypts the aggregate ciphertext per (proposal, decision) +pair, recovers the total ballot count via bounded discrete-log search, +and publishes the result with a Chaum–Pedersen proof of correct +decryption. + + +## El Gamal Encryption on Pallas + +The protocol uses additively homomorphic El Gamal encryption over the +Pallas curve [^protocol-pallasandvesta] to encrypt vote share amounts. + +Let $G$ be the Pallas $\mathsf{SpendAuthSig}^{\mathsf{Orchard}}$ +generator [^protocol-concretespendauthsig] and let +$\mathsf{ea}\_\mathsf{pk} = [\mathsf{ea}\_\mathsf{sk}]\, G$ be the election authority's +public key for the current voting round. + +To encrypt a ballot count $v$ with randomness $r$: + +$$\mathsf{Enc}(v, r) = \bigl([r]\, G, [v]\, G + [r]\, \mathsf{ea}\_\mathsf{pk}\bigr)$$ + +The ciphertext is a pair of Pallas points $(C_1, C_2)$. + +**Homomorphic property.** Given ciphertexts $\mathsf{Enc}(a, r_1)$ and +$\mathsf{Enc}(b, r_2)$, component-wise point addition yields a valid +encryption of $a + b$: + +$$\mathsf{Enc}(a, r_1) + \mathsf{Enc}(b, r_2) = \mathsf{Enc}(a + b, r_1 + r_2)$$ + +**Decryption.** Given ciphertext $(C_1, C_2)$ and secret key +$\mathsf{ea}\_\mathsf{sk}$: + +$$C_2 - [\mathsf{ea}\_\mathsf{sk}]\, C_1 = [v]\, G$$ + +The discrete logarithm $v$ is recovered via baby-step giant-step, which +is feasible because ballot counts are bounded (the total ZEC supply +yields at most $\approx 1.68 \times 10^8$ ballots). + + +## Data Structures + +### Vote Authority Note (VAN) + +A VAN represents spendable voting authority on the vote chain. Its +commitment is: + +$$\mathsf{van} = \mathsf{Poseidon}\bigl(\mathsf{DOMAIN\_VAN}, \mathsf{vpk}_{\mathsf{g\_d}}, \mathsf{vpk}_{\mathsf{pk\_d}}, \mathsf{num\_ballots}, \mathsf{voting}_{\mathsf{round\_id}}, \mathsf{proposal\_authority}, \mathsf{gov}_{\mathsf{comm\_rand}}\bigr)$$ + +where: + +- $\mathsf{DOMAIN}\_\mathsf{VAN} = 0$ — domain tag distinguishing VANs from VCs + in the shared VCT. +- $\mathsf{vpk}\_{\mathsf{g}\_\mathsf{d}} \in \mathbb{P}^*$ — diversified base of the + governance hotkey address. +- $\mathsf{vpk}\_{\mathsf{pk}\_\mathsf{d}} \in \mathbb{P}^*$ — diversified transmission + key of the governance hotkey address. +- $\mathsf{num}\_\mathsf{ballots} \in \{1 \ldots 2^{30}\}$ — total voting + weight in ballots. +- `voting_round_id` ∈ Pallas scalar — 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. Full + authority for 16 proposals is $2^{16} - 1 = 65535$. +- `gov_comm_rand` ∈ Pallas scalar — + commitment randomness. + +**VAN nullifier.** When a VAN is consumed (to vote or delegate), its +nullifier is: + +$$\mathsf{van}\_\mathsf{nullifier} = \mathsf{Poseidon}\bigl(\mathsf{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{nk}$ is the nullifier +deriving key from the holder's full viewing key. + +**Lifecycle.** A VAN is created during delegation (Phase 1), consumed +during voting (Phase 2) or further delegation, and replaced by a new VAN +with updated $\mathsf{proposal}\_\mathsf{authority}$ (voting) or split +$\mathsf{num}\_\mathsf{ballots}$ (delegation). + +### 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. +- `shares_hash` ∈ Pallas scalar — a hash + over blinded commitments to all $N_s$ encrypted shares (see + [Shares Hash]). +- $\mathsf{proposal}\_\mathsf{id} \in \{1 \ldots 16\}$ — which proposal this + vote targets. +- `vote_decision` ∈ Pallas scalar — the + voter's choice (0-indexed into the proposal's declared options). + +A VC is created during voting (Phase 2) and opened during share reveal +(Phase 4/5). The VC itself is never revealed on-chain; the Vote Reveal +Proof proves membership without exposing which VC is being opened. + +### Vote Share + +A vote share is one of the $N_s$ encrypted portions of a voter's ballot +count within a VC. + +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). +- `blind_i` ∈ Pallas scalar — 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})$$ + +where $C_{1,i,x}$ and $C_{2,i,x}$ denote the $x$-coordinates of the +ciphertext 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. + +### Vote Commitment Tree + +The VCT is an append-only Merkle tree of depth +`MerkleDepth_vct` that stores both VANs and VCs as leaves. +The tree MUST use the Poseidon hash function [^poseidon] over +the Pallas scalar field for all internal node hashing, with +the same instantiation used by the nullifier non-membership +tree [^balance-proof]. + +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 are 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 maintains 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 is append-only within a voting round. The vote chain rejects +any transaction that publishes a nullifier already present in the +corresponding set. + + +## Ballot Scaling + +Orchard note values are denominated in zatoshi. To reduce the bit-width +of values flowing through El Gamal encryption and downstream range +checks, the protocol converts 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. + +This conversion is enforced in the Delegation Proof circuit. The prover +witnesses $\mathsf{num}\_\mathsf{ballots}$ and a remainder $r$, and the circuit +constrains: + +1. $\mathsf{num}\_\mathsf{ballots} \times 12{,}500{,}000 + r = \sum v_i$ +2. $0 \leq r < 2^{24}$ +3. $\mathsf{num}\_\mathsf{ballots} \geq 1$ and $\mathsf{num}\_\mathsf{ballots} \leq 2^{30}$ + +The 30-bit upper bound on $\mathsf{num}\_\mathsf{ballots}$ accommodates up to +$\approx 134$ million ZEC — well above the 21 million ZEC supply cap. +The minimum of 1 ballot prevents dust delegations (holdings below +0.125 ZEC) from producing voting authority. + +
+ + +### Rationale for remainder range + + +The remainder 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. +
+ + +## 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 per-note ownership checks (note commitment integrity, Merkle path +validity, nullifier derivation, diversified address integrity, and +nullifier non-membership) are identical to those in 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. +- `voting_round_id` ⦂ Pallas scalar +- $\mathsf{cmx}\_\mathsf{new} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ — output note + commitment (to the governance hotkey address). + +#### 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: $\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). + +#### Conditions + +**Per-note conditions (5 note slots, with padding).** For each note +$i \in \{1 \ldots 5\}$, the following conditions from the Batched Claim +circuit [^balance-proof] apply: + +- Note commitment integrity. +- Merkle path validity in $\mathsf{rt}^{\mathsf{cm}}$ (skipped for padded + notes). +- Diversified address integrity (same $\mathsf{ivk}$ owns all notes). +- Standard nullifier derivation (kept private). +- Nullifier non-membership in $\mathsf{rt}^{\mathsf{excl}}$ (skipped for padded + notes). +- Padded notes have value 0. + +**Governance nullifier derivation.** For each real note $i$: + +$$\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 `tag_gov` is the field-element encoding of +`"governance authorization"` and `nf_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 `tag` = `tag_gov` and +`dom` = `voting_round_id`. + +**Signed note integrity.** The signed note is a dummy note with value 0. +Its note commitment $\mathsf{cm}^{\mathsf{signed}}$ is correctly constructed, and +$\mathsf{signed}\_{\mathsf{note}\_\mathsf{nullifier}}$ is correctly derived from it. + +**Rho binding.** The signed note's $\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.** $\mathsf{rk} = \mathsf{SpendAuthSig}^{\mathsf{Orchard}}\mathsf{.RandomizePublic}(\alpha, \mathsf{ak}^{\mathbb{P}})$. + +**Diversified address integrity for signed note.** The signed note's +address belongs to $(\mathsf{ak}, \mathsf{nk})$. + +**Output note commitment.** $\mathsf{cmx}\_\mathsf{new}$ is a correctly +constructed note commitment to an output note addressed to the +governance hotkey. + +**VAN integrity.** The public VAN commitment matches the claimed +governance hotkey, ballot count, round, and full proposal authority: + +$$\mathsf{van} = \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}}, \mathsf{gov}\_{\mathsf{comm}\_\mathsf{rand}}\bigr)$$ + +where $\mathsf{MAX}\_{\mathsf{PROPOSAL}\_\mathsf{AUTHORITY}} = 2^{16} - 1$. + +**Ballot scaling.** $\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 $\sigma$ as a valid $\mathsf{SpendAuthSig}^{\mathsf{Orchard}}$ + signature on the application-defined sighash, under $\mathsf{rk}$. +3. Verify that $\mathsf{rt}^{\mathsf{cm}}$ and $\mathsf{rt}^{\mathsf{excl}}$ correspond + to the published pool snapshot for $\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}$. +4. Verify that no $\mathsf{gov}\_{\mathsf{null}\_\mathsf{i}}$ appears in the governance + nullifier set. If any does, reject as a double-delegation. +5. Verify that $\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}$ matches an active round. +6. Insert $\mathsf{van}$ and $\mathsf{cmx}\_\mathsf{new}$ into the VCT. +7. Add all $\mathsf{gov}\_{\mathsf{null}\_\mathsf{i}}$ to the governance nullifier set. + +### Delegation Message + +A delegation transaction submitted to the vote chain MUST contain: + +| Field | Type | Description | +|---|---|---| +| `π_del` | Proof | The Delegation Proof | +| `σ_del` | Signature | SpendAuthSig under `rk` | +| `signed_note_nullifier` | Pallas scalar | Dummy note nullifier | +| `rk` | Pallas point | Randomized verification key | +| `rt_cm` | Pallas scalar | Note commitment tree root | +| `rt_excl` | Pallas scalar | Non-membership tree root | +| `van` | Pallas scalar | VAN commitment | +| `gov_null_1 … gov_null_5` | Pallas scalar each | Governance nullifiers | +| `voting_round_id` | Pallas scalar | Round identifier | +| `cmx_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. + +#### 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 16\}$ — which proposal. +- `voting_round_id` ⦂ Pallas scalar +- $\mathsf{ea}\_\mathsf{pk} ⦂ \mathbb{P}^*$ — election authority public key + (x and y coordinates). + +#### Auxiliary Inputs + +The prover knows: + +- `vote_decision` ⦂ Pallas scalar — the voter's choice. +- $\mathsf{vsk.ak} ⦂ \mathbb{P}^*$ — voting spend authorization + validating key. +- `vsk.nk` ⦂ Pallas scalar — nullifier + deriving key (same as `nk` from the holder's FVK). +- $\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 from the + VAN. +- $\mathsf{vpk}\_{\mathsf{pk}\_\mathsf{d}} ⦂ \mathbb{P}^*$ — diversified transmission + key from the VAN. +- $\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{1}, \ldots, \mathsf{v}_{N_s}$ — plaintext share values. +- $r_1, \ldots, r_{N_s}$ — El Gamal encryption randomness per share. +- $\mathsf{blind}\_\mathsf{1}, \ldots, \mathsf{blind}_{N_s}$ — per-share blind + factors. + +#### Conditions + +##### VAN Ownership and Spending + +**Condition 1 — Merkle tree membership.** The old VAN exists in the VCT: +$(\mathsf{path}^{\mathsf{vct}}, \mathsf{pos}^{\mathsf{vct}})$ is 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 old VAN commitment matches the +claimed fields: + +$$\mathsf{van}_{\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}}, \mathsf{gov}\_{\mathsf{comm}\_\mathsf{rand}}\bigr)$$ + +**Condition 3 — Diversified address integrity.** 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 `ivk_v` = CommitIvk`rivk_v`(ExtractP(`vsk.ak`), `vsk.nk`). + +**Condition 4 — Spend authority.** The randomized voting public key is +a valid rerandomization: + +$$\mathsf{r}\_\mathsf{vpk} = \mathsf{vsk.ak} + [\alpha_v]\, G$$ + +**Condition 5 — VAN nullifier.** 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"`. + +
+ + +### Rationale for VAN nullifier domain separation + + +The VAN nullifier and governance nullifier both use $\mathsf{nk}$ as the +Poseidon key (since $\mathsf{vsk.nk}$ and $\mathsf{nk}$ are the same +field element). Cross-circuit 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. +
+ +##### New VAN Construction + +**Condition 6 — Proposal authority decrement.** Bit +$\mathsf{proposal}\_\mathsf{id}$ is cleared in the authority bitmask: + +- $\mathsf{proposal}\_{\mathsf{authority}\_\mathsf{old}}$ is decomposed into 16 boolean + wires $b_0, \ldots, b_{15}$ that recompose to the original value. +- The bit at position $\mathsf{proposal}\_\mathsf{id}$ is asserted to be 1 + (the voter has authority for this proposal). +- $\mathsf{proposal}\_{\mathsf{authority}\_\mathsf{new}}$ is the recomposition with bit + $\mathsf{proposal}\_\mathsf{id}$ cleared; all other bits are unchanged. + +**Condition 7 — New VAN integrity.** The new VAN is correctly +constructed: + +$$\mathsf{van}_{\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}}, \mathsf{gov}\_{\mathsf{comm}\_\mathsf{rand}}\bigr)$$ + +The new VAN reuses the old VAN's diversified address and commitment +randomness; only $\mathsf{proposal}\_\mathsf{authority}$ changes. + +##### Vote Commitment Construction + +**Condition 8 — Shares sum correctness.** + +$$\sum_{i=1}^{N_s} \mathsf{v}\_\mathsf{i} = \mathsf{num}\_\mathsf{ballots}$$ + +**Condition 9 — Shares range check.** Each share is bounded: + +$$0 \leq \mathsf{v}\_\mathsf{i} < 2^{30} \quad \text{for each } i \in \{1 \ldots N_s\}$$ + +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 baby-step giant-step recovery at tally time. + +**Condition 10 — Shares hash integrity.** 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}\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.** 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 constrains equality on the $x$-coordinates of the computed +and witnessed ciphertext points. Constraining only $x$-coordinates is +sufficient because the shared randomness $r_i$ binds both curve points, +leaving no prover freedom in the $y$-coordinates. + +**Condition 12 — Vote commitment integrity.** 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. Verify $\sigma$ as a valid $\mathsf{SpendAuthSig}^{\mathsf{Orchard}}$ + signature on the application-defined sighash, under + $\mathsf{r}\_\mathsf{vpk}$. +3. Verify that $\mathsf{van}\_\mathsf{nullifier}$ does not appear in the VAN + nullifier set. If it does, reject as double-voting. +4. Verify that $\mathsf{rt}^{\mathsf{vct}}$ matches a published VCT root at + $\mathsf{anchor}\_\mathsf{height}$. +5. Verify that $\mathsf{proposal}\_\mathsf{id}$ is valid for the current round + and within the voting window. +6. Verify that $\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}$ matches an active round. +7. Verify that $\mathsf{ea}\_\mathsf{pk}$ matches the published election + authority public key for this round. +8. Insert $\mathsf{van}_{\mathsf{new}}$ and $\mathsf{vc}$ into the VCT. +9. Add $\mathsf{van}\_\mathsf{nullifier}$ to the VAN nullifier set. + +### 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 16\}$ | Proposal identifier | +| $\mathsf{voting}_{\mathsf{round\_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. + +#### Public Inputs + +Given a primary input: + +- `share_nullifier` ⦂ {0 .. $q_\mathbb{P}$ − 1} — + prevents double-counting. +- `enc_share` ⦂ $\mathbb{P}^* \times \mathbb{P}^*$ — the + El Gamal ciphertext $(C_1, C_2)$ for this share. +- `proposal_id` ⦂ {1 … 16} — which proposal. +- `vote_decision` ⦂ Pallas scalar — the voter's choice. +- `rt_vct` ⦂ {0 .. $q_\mathbb{P}$ − 1} — root of the + Vote Commitment Tree. +- `anchor_height` ⦂ integer — VCT snapshot height. +- `voting_round_id` ⦂ Pallas scalar + +#### Auxiliary Inputs + +The prover (submission server) knows: + +- `vc` ⦂ {0 .. $q_\mathbb{P}$ − 1} — the vote commitment + being opened (hidden from the verifier). +- `path_vct`, `pos_vct` — Merkle proof for the VC + in the VCT. +- `shares_hash` ⦂ Pallas scalar +- `share_index` ∈ {0, 1, …, $N_s$ − 1} — which share is being revealed. +- `enc_share_1` … `enc_share_Ns` — all + $N_s$ ciphertexts (to recompute `shares_hash`). +- `blind_1` … `blind_Ns` — all $N_s$ blind + factors. + +#### Conditions + +##### Vote Commitment Membership + +**Condition 1 — Merkle tree membership.** The VC exists in the VCT: +$(\mathsf{path}^{\mathsf{vct}}, \mathsf{pos}^{\mathsf{vct}})$ is 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 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 $\mathsf{shares}\_\mathsf{hash}$ is +recomputed from the witness ciphertexts and blind factors: + +$$\mathsf{share}\_{\mathsf{comm}\_\mathsf{i}} = \mathsf{Poseidon}\bigl(\mathsf{blind}\_\mathsf{i}, C_{1,i,x}, C_{2,i,x}\bigr) \quad \text{for each } i \in \{0 \ldots N_s - 1\}$$ + +$$\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}$ is constrained equal to the one +inside the VC (via condition 2). + +**Condition 4 — Share membership.** The public +$\mathsf{enc}\_\mathsf{share} = (C_1, C_2)$ matches the ciphertext at position +$\mathsf{share}\_\mathsf{index}$ in the committed set: + +$$\mathsf{Poseidon}\bigl(\mathsf{blind}_{\mathsf{share}\_\mathsf{index}}, C_{1,x}, C_{2,x}\bigr) = \mathsf{share}\_\mathsf{comms}[\mathsf{share}\_\mathsf{index}]$$ + +The circuit encodes $\mathsf{share}\_\mathsf{index}$ as a one-hot selector +vector over $N_s$ positions. The mux extracts the corresponding +$\mathsf{share}\_\mathsf{comm}$ via a dot product and constrains equality with +the commitment derived from the public ciphertext coordinates. Only the +blind factor for the revealed share is needed; the other $N_s - 1$ +blinds remain part of the full set used in condition 3 but are not +individually exposed. + +##### Nullifier + +**Condition 5 — Share nullifier.** 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 at + $\mathsf{anchor}\_\mathsf{height}$. +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. Add $\mathsf{share}\_\mathsf{nullifier}$ to the share nullifier set. +7. 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 16\}$ | Proposal identifier | +| $\mathsf{vote}_{\mathsf{decision}}$ | Pallas scalar | Vote decision | +| $\mathsf{rt}^{\mathsf{vct}}$ | Pallas scalar | VCT root | +| $\mathsf{anchor}_{\mathsf{height}}$ | integer | VCT anchor height | +| $\mathsf{voting}_{\mathsf{round\_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) | +| `enc_share_1` … `enc_share_Ns` | All $N_s$ ciphertexts | +| `blind_1` … `blind_Ns` | All $N_s$ blind factors | + +The server needs all $N_s$ ciphertexts and blind factors (not just the +one being revealed) to recompute $\mathsf{shares}\_\mathsf{hash}$ in +condition 3 of the Vote Reveal Proof. + + +## Submission Server + +The submission server is an untrusted party that constructs Vote Reveal +Proofs on behalf of voters. Delegating proof construction to a +server provides two benefits: + +- **Reliability.** A mobile client may be killed or lose connectivity + during proof generation. Servers are always-on. +- **Temporal unlinkability.** If a voter submitted all $N_s$ shares + directly, an observer could link them by timing. Servers stagger + submissions at randomized delays across the voting window, mixing + shares from many voters. + +For each payload received, the server: + +1. Waits a randomized delay. +2. Obtains the current VCT Merkle path for the VC. +3. Derives the share nullifier. +4. Constructs the Vote Reveal Proof (ZKP #3). +5. Submits the share reveal transaction to the vote chain. + +**What the server learns:** the encrypted share ciphertexts, proposal +identifier, and vote decision. **What it cannot learn:** the plaintext +share amounts (El Gamal encrypted under $\mathsf{ea}\_\mathsf{pk}$), the voter's +identity (the VC hides the link), or which VCT leaf the VC corresponds +to (the Merkle path is a private witness in the proof). + +Voters MAY distribute shares across multiple independent servers to +further limit any single server's view of their voting activity. +Specification of server selection, communication protocols, and timing +parameters is deferred to the operational voting process ZIP. + + +## Homomorphic Tally + +After the voting window closes, the tally proceeds in three steps. + +**Step 1 — Public aggregation.** For each +$(\mathsf{proposal}\_\mathsf{id}, \mathsf{vote}\_\mathsf{decision})$ pair, the aggregate +ciphertext is the component-wise sum of all revealed shares attributed +to that pair: + +$$\mathsf{agg} = \Bigl(\sum C_{1,j}, \sum C_{2,j}\Bigr)$$ + +This is publicly computable from on-chain data. Any party can +independently verify the aggregation. + +**Step 2 — EA decryption.** The election authority decrypts the +aggregate: + +$$\sum C_{2,j} - [\mathsf{ea}\_\mathsf{sk}]\, \sum C_{1,j} = [\mathsf{total}\_\mathsf{ballots}]\, G$$ + +and recovers $\mathsf{total}\_\mathsf{ballots}$ via baby-step giant-step +discrete-log search (feasible because the total is bounded by ZEC +supply). + +**Step 3 — Publish result with proof.** The EA publishes +$\mathsf{total}\_\mathsf{ballots}$ together with a Chaum–Pedersen discrete-log +equality proof [^chaum-pedersen] demonstrating that the same +$\mathsf{ea}\_\mathsf{sk}$ used to generate the published $\mathsf{ea}\_\mathsf{pk}$ was +used to decrypt the aggregate. Anyone can verify this proof against +the published $\mathsf{ea}\_\mathsf{pk}$, the on-chain aggregate ciphertext, and +the claimed $\mathsf{total}\_\mathsf{ballots}$. + +Individual vote amounts are never revealed — only the aggregate total +per (proposal, decision) pair. The tally decryption procedure and +Chaum–Pedersen proof construction are specified in [^ea-ceremony]. + + +## Election Authority Key + +Each voting round uses a fresh election authority keypair +$(\mathsf{ea}\_\mathsf{sk}, \mathsf{ea}\_\mathsf{pk})$ produced by an automated ceremony +among the vote chain's validator set. The ceremony generates +$\mathsf{ea}\_\mathsf{sk}$, distributes it to eligible validators via ECIES, and +transitions the round to active status once a quorum of validators have +acknowledged receipt. At tally time, any acknowledging validator can +decrypt the aggregate and publish a Chaum–Pedersen correctness proof. + +The ceremony protocol — including dealer selection, ECIES key +distribution, validator acknowledgment, confirmation thresholds, timeout +and jailing rules, and tally decryption procedures — is specified +in [^ea-ceremony]. + + +## Vote Chain + +The vote chain is a purpose-built chain that records all governance +transactions. It maintains: + +- The **Vote Commitment Tree** — an append-only Poseidon Merkle tree + storing VANs and VCs. +- Three **nullifier sets** — governance, VAN, and share nullifiers. +- An **encrypted share accumulator** — per + $(\mathsf{proposal}\_\mathsf{id}, \mathsf{vote}\_\mathsf{decision})$, the running + component-wise sum of revealed El Gamal ciphertexts. + +For each transaction type, the chain verifies the corresponding proof, +checks nullifier freshness, updates the VCT, and (for share reveals) +accumulates the ciphertext. All verification is ZKP-based — the chain +never observes plaintext vote amounts or voter identities. + +The vote chain's consensus mechanism, block structure, transaction +encoding, and API are out of scope for this ZIP. + + +# 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 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 +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 +bounded discrete-log search at tally time has a smaller search space. +The 0.125 ZEC minimum also prevents dust delegations from bloating vote +chain state. + +## 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 +randomized times, 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. + +## 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})$. +Without blinding, an observer could compute +$\mathsf{Poseidon}(C_{1,i,x}, C_{2,i,x})$ 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 Server-Delegated Share Reveal + +The Vote Reveal Proof (ZKP #3) 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 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. + + +# Reference implementation + +A reference implementation of the three ZKP circuits (Delegation Proof, +Vote Proof, and Vote Reveal Proof) and the vote chain state machine +exists in the coinholder voting codebase used for this design. + +At the time of writing, some implementation repositories are not +publicly accessible. Public, stable links will be added before +finalization of this ZIP. + + +# Open issues + +- The Poseidon instantiation ($\mathsf{P128Pow5T3}$) and round + constants should be explicitly referenced or pinned once a canonical + parameter set for Zcash usage is published. +- The exact sighash construction for the Delegation Proof and Vote + Proof (what is signed by the spend authorization key, and how it + binds to the proof's public inputs) requires further specification. +- The depth of the Vote Commitment Tree + ($\mathsf{MerkleDepth}^{\mathsf{vct}}$) should be specified based on expected + capacity requirements. +- The encoding of domain separator strings (`"governance authorization"`, + `"vote authority spend"`, `"share spend"`) as field elements should be + pinned to a specific encoding procedure. +- The number of shares $N_s = 16$ is a design target; the current + implementation uses a smaller value. The final parameter should be + confirmed based on circuit size and proving time benchmarks. +- The interaction between delegation (VAN splitting) and the Delegation + Proof circuit (which produces a single VAN) should be specified for + the delegation extension. +- The $\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}$ derivation procedure should be + specified to ensure uniqueness across rounds. + + +# 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-pallasandvesta]: [Zcash Protocol Specification, Version 2025.6.3 [NU6.1]. Section 5.4.9.6: Pallas and Vesta](protocol/protocol.pdf#pallasandvesta) + +[^protocol-concretespendauthsig]: [Zcash Protocol Specification, Version 2025.6.3 [NU6.1]. Section 5.4.7.1: Spend Authorization Signature (Orchard)](protocol/protocol.pdf#concretespendauthsig) + +[^poseidon]: [Poseidon: A New Hash Function for Zero-Knowledge Proof Systems](https://eprint.iacr.org/2019/458) + +[^balance-proof]: [Draft ZIP: Orchard Proof-of-Balance](draft-valargroup-orchard-balance-proof) + +[^pir-governance]: [Draft ZIP: Private Information Retrieval for Nullifier Exclusion Proofs](draft-valargroup-nullifier-pir) + +[^ea-ceremony]: [Draft ZIP: Election Authority Key Ceremony](draft-valargroup-ea-key-ceremony) + +[^chaum-pedersen]: [Chaum, D. and Pedersen, T.P. "Wallet Databases with Observers." CRYPTO 1992](https://link.springer.com/chapter/10.1007/3-540-48071-4_7) From 220ade9209eff039cf894834f49a536c113ea624 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 4 Mar 2026 20:10:23 -0300 Subject: [PATCH 02/56] add note on TSS Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 149 +++++++++++++++-------- 1 file changed, 95 insertions(+), 54 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index a276b17f0..ebd2d97a3 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -63,9 +63,10 @@ Voting round Election authority (EA) -: The entity (or set of validators) that holds the El Gamal secret key - $\mathsf{ea}\_\mathsf{sk}$ for a voting round and is responsible for decrypting - the aggregate tally after the voting window closes. +: The set of validators that collectively hold Shamir shares of the + El Gamal secret key $\mathsf{ea}\_\mathsf{sk}$ for a voting round. No single + validator holds the full key. At tally time, $t$ validators cooperate + to produce partial decryptions of the aggregate ciphertext. Submission server @@ -109,10 +110,10 @@ 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. The election authority -decrypts the aggregate totals and publishes results with a -Chaum–Pedersen proof of correct decryption. Individual vote amounts are -never revealed. +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 and verified via Chaum–Pedersen +DLEQ proofs. Individual vote amounts are never revealed. # Motivation @@ -172,15 +173,18 @@ 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.** The election authority holds $\mathsf{ea}\_\mathsf{sk}$ and -can in principle decrypt individual share ciphertexts. Privacy against -the EA relies on vote splitting: the EA sees encrypted shares but cannot -link them to specific voters or vote commitments. Submission servers -learn the encrypted share ciphertext, proposal identifier, and vote -decision for each share they submit, 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 mitigates this risk. +**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 2n/3 \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, proposal +identifier, and vote decision for each share they submit, 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 mitigates this risk. **Non-membership tree queries.** Obtaining exclusion proofs for the nullifier non-membership tree during delegation requires querying a data @@ -212,13 +216,15 @@ see [^pir-governance]. - The consensus mechanism and operational parameters of the vote chain are out of scope for this ZIP. -- The election authority key ceremony (generation and distribution of - $\mathsf{ea}\_\mathsf{sk}$) is specified separately in [^ea-ceremony]. +- 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 expected to be specified in a separate ZIP. -- Threshold decryption (splitting $\mathsf{ea}\_\mathsf{sk}$ among multiple - parties) is out of scope. +- Distributed key generation (producing $\mathsf{ea}\_\mathsf{pk}$ without any + party constructing $\mathsf{ea}\_\mathsf{sk}$) is out of scope; the current + design uses a trusted dealer. - Post-quantum security of the El Gamal encryption layer is out of scope. - Privacy-preserving retrieval of nullifier non-membership proofs is @@ -254,11 +260,12 @@ revealing which one) and submits it to the vote chain at a randomized delay. The chain accumulates the revealed El Gamal ciphertexts homomorphically. -**Phase 5 — Tally.** After the voting window closes, the election -authority decrypts the aggregate ciphertext per (proposal, decision) -pair, recovers the total ballot count via bounded discrete-log search, -and publishes the result with a Chaum–Pedersen proof of correct -decryption. +**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 combined via +Lagrange interpolation to recover the total ballot count (via bounded +discrete-log search), with correctness verified through per-validator +Chaum–Pedersen DLEQ proofs. ## El Gamal Encryption on Pallas @@ -1011,42 +1018,55 @@ $$\mathsf{agg} = \Bigl(\sum C_{1,j}, \sum C_{2,j}\Bigr)$$ This is publicly computable from on-chain data. Any party can independently verify the aggregation. -**Step 2 — EA decryption.** The election authority decrypts the -aggregate: - -$$\sum C_{2,j} - [\mathsf{ea}\_\mathsf{sk}]\, \sum C_{1,j} = [\mathsf{total}\_\mathsf{ballots}]\, G$$ - -and recovers $\mathsf{total}\_\mathsf{ballots}$ via baby-step giant-step -discrete-log search (feasible because the total is bounded by ZEC -supply). - -**Step 3 — Publish result with proof.** The EA publishes -$\mathsf{total}\_\mathsf{ballots}$ together with a Chaum–Pedersen discrete-log -equality proof [^chaum-pedersen] demonstrating that the same -$\mathsf{ea}\_\mathsf{sk}$ used to generate the published $\mathsf{ea}\_\mathsf{pk}$ was -used to decrypt the aggregate. Anyone can verify this proof against -the published $\mathsf{ea}\_\mathsf{pk}$, the on-chain aggregate ciphertext, and -the claimed $\mathsf{total}\_\mathsf{ballots}$. +**Step 2 — Threshold decryption.** At least $t$ validators each produce +a partial decryption of the aggregate ciphertext using their Shamir +share, accompanied by a Chaum–Pedersen DLEQ proof [^chaum-pedersen] of +correctness. The partial decryptions are combined via Lagrange +interpolation: + +$$\sum C_{2,j} - \sum_{i \in S} [\lambda_i]\, D_i = [\mathsf{total}\_\mathsf{ballots}]\, G$$ + +where $D_i = [s_i]\, \sum C_{1,j}$ is validator $V_i$'s partial +decryption and $\lambda_i$ are the Lagrange coefficients for the +participating set $S$. The value $\mathsf{total}\_\mathsf{ballots}$ is recovered +via baby-step giant-step discrete-log search (feasible because the total +is bounded by ZEC supply). No party reconstructs $\mathsf{ea}\_\mathsf{sk}$ +during this process. + +**Step 3 — Publish result with proof.** The proposer publishes +$\mathsf{total}\_\mathsf{ballots}$ together with the individual partial +decryptions and their DLEQ proofs. Anyone can verify each partial +decryption against the validator's public share commitment (derived from +the Feldman commitments published during the key ceremony), recompute +the Lagrange combination, and confirm the claimed +$\mathsf{total}\_\mathsf{ballots}$. Individual vote amounts are never revealed — only the aggregate total -per (proposal, decision) pair. The tally decryption procedure and -Chaum–Pedersen proof construction are specified in [^ea-ceremony]. +per (proposal, decision) pair. The threshold decryption procedure is specified in [^ea-ceremony]. ## Election Authority Key Each voting round uses a fresh election authority keypair -$(\mathsf{ea}\_\mathsf{sk}, \mathsf{ea}\_\mathsf{pk})$ produced by an automated ceremony -among the vote chain's validator set. The ceremony generates -$\mathsf{ea}\_\mathsf{sk}$, distributes it to eligible validators via ECIES, and -transitions the round to active status once a quorum of validators have -acknowledged receipt. At tally time, any acknowledging validator can -decrypt the aggregate and publish a Chaum–Pedersen correctness proof. - -The ceremony protocol — including dealer selection, ECIES key -distribution, validator acknowledgment, confirmation thresholds, timeout -and jailing rules, and tally decryption procedures — is specified -in [^ea-ceremony]. +$(\mathsf{ea}\_\mathsf{sk}, \mathsf{ea}\_\mathsf{pk})$ produced by an automated threshold +secret sharing ceremony among the vote chain's validator set. A trusted +dealer generates $\mathsf{ea}\_\mathsf{sk}$, splits it into Shamir shares, distributes the shares to eligible validators via +ECIES, and deletes the full key. After the ceremony, no single party +holds $\mathsf{ea}\_\mathsf{sk}$; each validator holds only its share. The round +transitions to active status once a quorum of validators have verified +their shares and acknowledged receipt. + +At tally time, at least $t = \lceil 2n/3 \rceil + 1$ validators +cooperate to produce partial decryptions of the aggregate ciphertext. +Each partial decryption is accompanied by a Chaum–Pedersen DLEQ proof +verifiable against the validator's public share commitment. The partial +decryptions are combined via Lagrange interpolation — the full secret +key is never reconstructed. + +The ceremony protocol — including dealer selection, Feldman VSS +construction, ECIES share distribution, validator acknowledgment, +confirmation thresholds, timeout and jailing rules, and threshold +decryption procedures — is specified in [^ea-ceremony]. ## Vote Chain @@ -1142,6 +1162,27 @@ externally observable (both old and new commitments appear as opaque field elements in the VCT), so address rotation would provide no additional unlinkability. +## Why Threshold Secret Sharing + +The election authority key is split into Shamir shares rather than distributed intact to all validators. This +ensures that compromise of any single validator (or any minority below +$t$) does not expose the full decryption key. The threshold +$t = \lceil 2n/3 \rceil + 1$ aligns with the CometBFT supermajority +assumption: an adversary that controls fewer than one-third of +validators cannot reach the decryption threshold, so the existing +consensus trust boundary extends to vote-amount privacy. + +The protocol uses a trusted dealer rather than +distributed key generation (DKG). + + +For initial scope, we have consdered Feldman VSS buthave opted out. We have to trust the dealer and validators to correctly distribute the shares. This is a correctness problem which would be caught at tally if tampered with. + +The following are left as consideration for future scope: +- Feldman commitment from dealer to prove they submitted the right share +- DLEQ proofs for validators to confirm that they used a correct share +- Distributed key generation + ## Why Domain Tags in the VCT Both VANs and VCs are leaves in the same Merkle tree. The domain tags From 8df9724ef6c70ef4054b57e212df14b9a6b3e1dc Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 4 Mar 2026 20:38:22 -0300 Subject: [PATCH 03/56] fix formatting for math to format locally Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 84 ++++++++++++------------ 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index ebd2d97a3..f2af5acd5 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -319,11 +319,11 @@ where: key of the governance hotkey address. - $\mathsf{num}\_\mathsf{ballots} \in \{1 \ldots 2^{30}\}$ — total voting weight in ballots. -- `voting_round_id` ∈ Pallas scalar — scopes this VAN to a specific voting round. +- $\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. Full authority for 16 proposals is $2^{16} - 1 = 65535$. -- `gov_comm_rand` ∈ Pallas scalar — +- $\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 @@ -349,12 +349,12 @@ $$\mathsf{vc} = \mathsf{Poseidon}\bigl(\mathsf{DOMAIN}\_\mathsf{VC}, \mathsf{vot where: - $\mathsf{DOMAIN}\_\mathsf{VC} = 1$ — domain tag. -- `shares_hash` ∈ Pallas scalar — a hash +- $\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 16\}$ — which proposal this vote targets. -- `vote_decision` ∈ Pallas scalar — the +- $\mathsf{vote}\_\mathsf{decision} \in \{ 0 .. q_{\mathbb{P}}-1 \}$ — the voter's choice (0-indexed into the proposal's declared options). A VC is created during voting (Phase 2) and opened during share reveal @@ -371,7 +371,7 @@ 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). -- `blind_i` ∈ Pallas scalar — per-share +- $\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. @@ -404,7 +404,7 @@ $\mathsf{blind}$ is the blind factor for the revealed share. ### Vote Commitment Tree The VCT is an append-only Merkle tree of depth -`MerkleDepth_vct` that stores both VANs and VCs as leaves. +$\mathsf{MerkleDepth}^{\mathsf{vct}}$ that stores both VANs and VCs as leaves. The tree MUST use the Poseidon hash function [^poseidon] over the Pallas scalar field for all internal node hashing, with the same instantiation used by the nullifier non-membership @@ -502,7 +502,7 @@ Given a primary input: 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. -- `voting_round_id` ⦂ Pallas scalar +- $\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). @@ -540,12 +540,12 @@ circuit [^balance-proof] apply: $$\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 `tag_gov` is the field-element encoding of -`"governance authorization"` and `nf_old_i` is the note's +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 `tag` = `tag_gov` and -`dom` = `voting_round_id`. +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 is a dummy note with value 0. Its note commitment $\mathsf{cm}^{\mathsf{signed}}$ is correctly constructed, and @@ -600,16 +600,16 @@ A delegation transaction submitted to the vote chain MUST contain: | Field | Type | Description | |---|---|---| -| `π_del` | Proof | The Delegation Proof | -| `σ_del` | Signature | SpendAuthSig under `rk` | -| `signed_note_nullifier` | Pallas scalar | Dummy note nullifier | -| `rk` | Pallas point | Randomized verification key | -| `rt_cm` | Pallas scalar | Note commitment tree root | -| `rt_excl` | Pallas scalar | Non-membership tree root | -| `van` | Pallas scalar | VAN commitment | -| `gov_null_1 … gov_null_5` | Pallas scalar each | Governance nullifiers | -| `voting_round_id` | Pallas scalar | Round identifier | -| `cmx_new` | Pallas scalar | Output note commitment | +| $\pi\_\mathsf{del}$ | Proof | The Delegation Proof | +| $\sigma\_\mathsf{del}$ | Signature | SpendAuthSig under $\mathsf{rk}$ | +| $\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 @@ -636,7 +636,7 @@ Given a primary input: Vote Commitment Tree. - $\mathsf{anchor}\_\mathsf{height} ⦂ \mathbb{N}$ — VCT snapshot height. - $\mathsf{proposal}\_\mathsf{id} ⦂ \{1 \ldots 16\}$ — which proposal. -- `voting_round_id` ⦂ Pallas scalar +- $\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). @@ -644,11 +644,11 @@ Given a primary input: The prover knows: -- `vote_decision` ⦂ Pallas scalar — the voter's choice. +- $\mathsf{vote}\_\mathsf{decision} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ — the voter's choice. - $\mathsf{vsk.ak} ⦂ \mathbb{P}^*$ — voting spend authorization validating key. -- `vsk.nk` ⦂ Pallas scalar — nullifier - deriving key (same as `nk` from the holder's FVK). +- $\mathsf{vsk.nk} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ — nullifier + deriving key (same as $\mathsf{nk}$ from the holder's FVK). - $\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. @@ -688,7 +688,7 @@ belongs to the voting key: $$\mathsf{vpk}\_{\mathsf{pk}\_\mathsf{d}} = [\mathsf{ivk}\_\mathsf{v}]\, \mathsf{vpk}\_{\mathsf{g}\_\mathsf{d}}$$ -where `ivk_v` = CommitIvk`rivk_v`(ExtractP(`vsk.ak`), `vsk.nk`). +where $\mathsf{ivk}\_\mathsf{v} = \mathsf{CommitIvk}_{\mathsf{rivk}\_\mathsf{v}}\!\bigl(\mathsf{Extract}_{\mathbb{P}}(\mathsf{vsk.ak}),\; \mathsf{vsk.nk}\bigr)$. **Condition 4 — Spend authority.** The randomized voting public key is a valid rerandomization: @@ -830,30 +830,30 @@ submission server, not the voter. Given a primary input: -- `share_nullifier` ⦂ {0 .. $q_\mathbb{P}$ − 1} — +- $\mathsf{share}\_\mathsf{nullifier} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ — prevents double-counting. -- `enc_share` ⦂ $\mathbb{P}^* \times \mathbb{P}^*$ — the +- $\mathsf{enc}\_\mathsf{share} ⦂ \mathbb{P}^* \times \mathbb{P}^*$ — the El Gamal ciphertext $(C_1, C_2)$ for this share. -- `proposal_id` ⦂ {1 … 16} — which proposal. -- `vote_decision` ⦂ Pallas scalar — the voter's choice. -- `rt_vct` ⦂ {0 .. $q_\mathbb{P}$ − 1} — root of the +- $\mathsf{proposal}\_\mathsf{id} ⦂ \{1 \ldots 16\}$ — 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. -- `anchor_height` ⦂ integer — VCT snapshot height. -- `voting_round_id` ⦂ Pallas scalar +- $\mathsf{anchor}\_\mathsf{height} ⦂ \mathbb{N}$ — VCT snapshot height. +- $\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ #### Auxiliary Inputs The prover (submission server) knows: -- `vc` ⦂ {0 .. $q_\mathbb{P}$ − 1} — the vote commitment +- $\mathsf{vc} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ — the vote commitment being opened (hidden from the verifier). -- `path_vct`, `pos_vct` — Merkle proof for the VC +- $\mathsf{path}^{\mathsf{vct}}, \mathsf{pos}^{\mathsf{vct}}$ — Merkle proof for the VC in the VCT. -- `shares_hash` ⦂ Pallas scalar -- `share_index` ∈ {0, 1, …, $N_s$ − 1} — which share is being revealed. -- `enc_share_1` … `enc_share_Ns` — all - $N_s$ ciphertexts (to recompute `shares_hash`). -- `blind_1` … `blind_Ns` — all $N_s$ blind +- $\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{enc}\_{\mathsf{share}\_1} \ldots \mathsf{enc}\_{\mathsf{share}\_{N_s}}$ — all + $N_s$ ciphertexts (to recompute $\mathsf{shares}\_\mathsf{hash}$). +- $\mathsf{blind}\_1 \ldots \mathsf{blind}_{N_s}$ — all $N_s$ blind factors. #### Conditions @@ -963,8 +963,8 @@ payload. For share $i$, the payload MUST contain: | $\mathsf{proposal}_{\mathsf{id}}$ | Proposal identifier | | $\mathsf{vote}_{\mathsf{decision}}$ | Vote decision | | $\mathsf{share}_{\mathsf{index}}$ | Which share to reveal (0-indexed) | -| `enc_share_1` … `enc_share_Ns` | All $N_s$ ciphertexts | -| `blind_1` … `blind_Ns` | All $N_s$ blind factors | +| $\mathsf{enc}\_{\mathsf{share}\_1} \ldots \mathsf{enc}\_{\mathsf{share}\_{N_s}}$ | All $N_s$ ciphertexts | +| $\mathsf{blind}\_1 \ldots \mathsf{blind}_{N_s}$ | All $N_s$ blind factors | The server needs all $N_s$ ciphertexts and blind factors (not just the one being revealed) to recompute $\mathsf{shares}\_\mathsf{hash}$ in From 7929b35a0ff03077b59f5d18f38230f0a77b504c Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 4 Mar 2026 20:55:28 -0300 Subject: [PATCH 04/56] Fix detail about helper server learning one blind, not all Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 74 ++++++++++++++++-------- 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index f2af5acd5..d10a34e08 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -180,9 +180,10 @@ $t = \lceil 2n/3 \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, proposal -identifier, and vote decision for each share they submit, but cannot -decrypt plaintext amounts or link shares to voter identities. The +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 mitigates this risk. @@ -851,10 +852,11 @@ The prover (submission server) knows: 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{enc}\_{\mathsf{share}\_1} \ldots \mathsf{enc}\_{\mathsf{share}\_{N_s}}$ — all - $N_s$ ciphertexts (to recompute $\mathsf{shares}\_\mathsf{hash}$). -- $\mathsf{blind}\_1 \ldots \mathsf{blind}_{N_s}$ — all $N_s$ blind - factors. +- $\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 @@ -877,14 +879,14 @@ revealed share is attributed to the correct proposal and decision. ##### Share Opening **Condition 3 — Shares hash integrity.** The $\mathsf{shares}\_\mathsf{hash}$ is -recomputed from the witness ciphertexts and blind factors: - -$$\mathsf{share}\_{\mathsf{comm}\_\mathsf{i}} = \mathsf{Poseidon}\bigl(\mathsf{blind}\_\mathsf{i}, C_{1,i,x}, C_{2,i,x}\bigr) \quad \text{for each } i \in \{0 \ldots N_s - 1\}$$ +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}$ is constrained equal to the one -inside the VC (via condition 2). +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 public $\mathsf{enc}\_\mathsf{share} = (C_1, C_2)$ matches the ciphertext at position @@ -895,10 +897,11 @@ $$\mathsf{Poseidon}\bigl(\mathsf{blind}_{\mathsf{share}\_\mathsf{index}}, C_{1,x The circuit encodes $\mathsf{share}\_\mathsf{index}$ as a one-hot selector vector over $N_s$ positions. The mux extracts the corresponding $\mathsf{share}\_\mathsf{comm}$ via a dot product and constrains equality with -the commitment derived from the public ciphertext coordinates. Only the -blind factor for the revealed share is needed; the other $N_s - 1$ -blinds remain part of the full set used in condition 3 but are not -individually exposed. +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 @@ -963,12 +966,17 @@ payload. For share $i$, the payload MUST contain: | $\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}\_1} \ldots \mathsf{enc}\_{\mathsf{share}\_{N_s}}$ | All $N_s$ ciphertexts | -| $\mathsf{blind}\_1 \ldots \mathsf{blind}_{N_s}$ | All $N_s$ blind factors | +| $\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 | -The server needs all $N_s$ ciphertexts and blind factors (not just the -one being revealed) to recompute $\mathsf{shares}\_\mathsf{hash}$ in -condition 3 of the Vote Reveal Proof. +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]). ## Submission Server @@ -992,11 +1000,14 @@ For each payload received, the server: 4. Constructs the Vote Reveal Proof (ZKP #3). 5. Submits the share reveal transaction to the vote chain. -**What the server learns:** the encrypted share ciphertexts, proposal -identifier, and vote decision. **What it cannot learn:** the plaintext -share amounts (El Gamal encrypted under $\mathsf{ea}\_\mathsf{pk}$), the voter's -identity (the VC hides the link), or which VCT leaf the VC corresponds -to (the Merkle path is a private witness in the proof). +**What the server learns:** the encrypted share ciphertext and blind +factor for a single share, the blinded share commitments for all $N_s$ +shares, the proposal identifier, and the vote decision. **What it +cannot learn:** the plaintext share amount (El Gamal encrypted under +$\mathsf{ea}\_\mathsf{pk}$), the ciphertexts or blind factors of other +shares (hidden behind blinded commitments), the voter's identity (the +VC hides the link), or which VCT leaf the VC corresponds to (the +Merkle path is a private witness in the proof). Voters MAY distribute shares across multiple independent servers to further limit any single server's view of their voting activity. @@ -1141,6 +1152,19 @@ 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 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 (ZKP #3) is constructed by the submission server From 0f68a40ed029a5f667fb0489290124c52793c2b3 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 4 Mar 2026 21:10:30 -0300 Subject: [PATCH 05/56] fix formatting Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 39 ++++++++++++------------ 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index d10a34e08..c1c91b8ce 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -806,14 +806,14 @@ A vote transaction submitted to the vote chain MUST contain: |---|---|---| | $\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{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 16\}$ | Proposal identifier | -| $\mathsf{voting}_{\mathsf{round\_id}}$ | Pallas scalar | Round identifier | +| $\mathsf{anchor}\_\mathsf{height}$ | integer | VCT anchor height | +| $\mathsf{proposal}\_\mathsf{id}$ | $\{1 \ldots 16\}$ | Proposal identifier | +| $\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}$ | Pallas scalar | Round identifier | | $\mathsf{ea}_{\mathsf{pk}}$ | Pallas point | EA public key | @@ -888,9 +888,10 @@ 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 public -$\mathsf{enc}\_\mathsf{share} = (C_1, C_2)$ matches the ciphertext at position -$\mathsf{share}\_\mathsf{index}$ in the committed set: +**Condition 4 — Share membership.** The commitment derived from the +public $\mathsf{enc}\_\mathsf{share} = (C_1, C_2)$ 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}\bigr) = \mathsf{share}\_\mathsf{comms}[\mathsf{share}\_\mathsf{index}]$$ @@ -942,13 +943,13 @@ 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 16\}$ | Proposal identifier | -| $\mathsf{vote}_{\mathsf{decision}}$ | Pallas scalar | Vote decision | +| $\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 16\}$ | Proposal identifier | +| $\mathsf{vote}\_\mathsf{decision}$ | Pallas scalar | Vote decision | | $\mathsf{rt}^{\mathsf{vct}}$ | Pallas scalar | VCT root | -| $\mathsf{anchor}_{\mathsf{height}}$ | integer | VCT anchor height | -| $\mathsf{voting}_{\mathsf{round\_id}}$ | Pallas scalar | Round identifier | +| $\mathsf{anchor}\_\mathsf{height}$ | integer | VCT anchor height | +| $\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. @@ -962,10 +963,10 @@ payload. For share $i$, the payload MUST contain: |---|---| | $\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{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 | @@ -1046,9 +1047,7 @@ during this process. **Step 3 — Publish result with proof.** The proposer publishes $\mathsf{total}\_\mathsf{ballots}$ together with the individual partial -decryptions and their DLEQ proofs. Anyone can verify each partial -decryption against the validator's public share commitment (derived from -the Feldman commitments published during the key ceremony), recompute +decryptions and their DLEQ proofs. Anyone can recompute the Lagrange combination, and confirm the claimed $\mathsf{total}\_\mathsf{ballots}$. From 3d0de033489ae324a99ac4b9878c25b76c69cab8 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 4 Mar 2026 21:21:33 -0300 Subject: [PATCH 06/56] update open issues Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 91 ++++++++++++++++-------- 1 file changed, 63 insertions(+), 28 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index c1c91b8ce..533e0990c 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -112,8 +112,9 @@ 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 and verified via Chaum–Pedersen -DLEQ proofs. Individual vote amounts are never revealed. +decryptions; the results are combined via Lagrange interpolation and +the aggregate total is publicly verified. Individual vote amounts are +never revealed. # Motivation @@ -263,10 +264,11 @@ 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 combined via +(proposal, decision) pair. The partial decryptions are stored on-chain and combined via Lagrange interpolation to recover the total ballot count (via bounded -discrete-log search), with correctness verified through per-validator -Chaum–Pedersen DLEQ proofs. +discrete-log search). Correctness is publicly verifiable: anyone can +recompute the Lagrange combination from the on-chain partial +decryptions. ## El Gamal Encryption on Pallas @@ -1032,9 +1034,8 @@ independently verify the aggregation. **Step 2 — Threshold decryption.** At least $t$ validators each produce a partial decryption of the aggregate ciphertext using their Shamir -share, accompanied by a Chaum–Pedersen DLEQ proof [^chaum-pedersen] of -correctness. The partial decryptions are combined via Lagrange -interpolation: +share and post it on-chain. The partial decryptions are combined via +Lagrange interpolation: $$\sum C_{2,j} - \sum_{i \in S} [\lambda_i]\, D_i = [\mathsf{total}\_\mathsf{ballots}]\, G$$ @@ -1045,11 +1046,10 @@ via baby-step giant-step discrete-log search (feasible because the total is bounded by ZEC supply). No party reconstructs $\mathsf{ea}\_\mathsf{sk}$ during this process. -**Step 3 — Publish result with proof.** The proposer publishes -$\mathsf{total}\_\mathsf{ballots}$ together with the individual partial -decryptions and their DLEQ proofs. Anyone can recompute -the Lagrange combination, and confirm the claimed -$\mathsf{total}\_\mathsf{ballots}$. +**Step 3 — Public verification.** The individual partial decryptions +$D_i$ and the aggregate ciphertext are all on-chain. Anyone can +recompute the Lagrange combination for any qualifying subset of $t$ +validators and confirm the claimed $\mathsf{total}\_\mathsf{ballots}$. Individual vote amounts are never revealed — only the aggregate total per (proposal, decision) pair. The threshold decryption procedure is specified in [^ea-ceremony]. @@ -1067,11 +1067,9 @@ transitions to active status once a quorum of validators have verified their shares and acknowledged receipt. At tally time, at least $t = \lceil 2n/3 \rceil + 1$ validators -cooperate to produce partial decryptions of the aggregate ciphertext. -Each partial decryption is accompanied by a Chaum–Pedersen DLEQ proof -verifiable against the validator's public share commitment. The partial -decryptions are combined via Lagrange interpolation — the full secret -key is never reconstructed. +cooperate to produce partial decryptions of the aggregate ciphertext +and post them on-chain. The partial decryptions are combined via +Lagrange interpolation — the full secret key is never reconstructed. The ceremony protocol — including dealer selection, Feldman VSS construction, ECIES share distribution, validator acknowledgment, @@ -1195,16 +1193,42 @@ assumption: an adversary that controls fewer than one-third of validators cannot reach the decryption threshold, so the existing consensus trust boundary extends to vote-amount privacy. -The protocol uses a trusted dealer rather than -distributed key generation (DKG). - - -For initial scope, we have consdered Feldman VSS buthave opted out. We have to trust the dealer and validators to correctly distribute the shares. This is a correctness problem which would be caught at tally if tampered with. - -The following are left as consideration for future scope: -- Feldman commitment from dealer to prove they submitted the right share -- DLEQ proofs for validators to confirm that they used a correct share -- Distributed key generation +The protocol uses a trusted dealer rather than distributed key +generation (DKG). Feldman VSS commitments (which would let each +validator verify that its share is consistent with +$\mathsf{ea}\_\mathsf{pk}$) are omitted for initial scope; the dealer is +trusted to distribute correct shares, and any tampering would be +caught at tally time. Distributed key generation is a potential future +enhancement (see [Open issues]). + +## Why No Per-Validator DLEQ Proofs + +Each validator's partial decryption $D_i = [s_i]\, \sum C_{1,j}$ is +posted on-chain without a Chaum–Pedersen DLEQ proof [^chaum-pedersen] +attesting that $s_i$ matches the validator's committed share. This is a +deliberate simplification. + +Without DLEQ proofs, a malicious validator can post a bogus $D_i$. Any +Lagrange combination that includes this $D_i$ will produce an incorrect +message point, causing the baby-step giant-step search to fail or +return an implausible result. However, a bogus $D_i$ cannot cause a +wrong tally to be accepted: the decrypted result is publicly +verifiable (anyone can check that the claimed +$\mathsf{total}\_\mathsf{ballots}$ satisfies the decryption equation for +the on-chain aggregate ciphertext), so a tally derived from a corrupted +subset will always be detected. + +The missing DLEQ proofs are therefore a liveness issue, not a +correctness one. A single malicious validator can force the tally +procedure to try alternative subsets of size $t$ to find a fully honest +quorum, delaying finalization. Under the CometBFT assumption that fewer +than one-third of validators are Byzantine, such an honest quorum +always exists. + +Adding per-validator DLEQ proofs would allow immediate identification +and exclusion of misbehaving validators, reducing verification to +$O(1)$ per partial decryption. This is left as a future enhancement +(see [Open issues]). ## Why Domain Tags in the VCT @@ -1255,6 +1279,17 @@ finalization of this ZIP. the delegation extension. - The $\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}$ derivation procedure should be specified to ensure uniqueness across rounds. +- Per-validator Chaum–Pedersen DLEQ proofs for partial decryptions + would allow immediate identification of misbehaving validators at + tally time, converting the current liveness cost (subset search) to + $O(1)$ verification. See [Why No Per-Validator DLEQ Proofs]. +- Feldman VSS commitments from the dealer during the key ceremony + would let each validator verify that its Shamir share is consistent + with $\mathsf{ea}\_\mathsf{pk}$, removing the trust assumption on the + dealer. See [Why Threshold Secret Sharing]. +- Distributed key generation (DKG) would eliminate the trusted dealer + entirely, producing $\mathsf{ea}\_\mathsf{pk}$ without any single party + ever holding $\mathsf{ea}\_\mathsf{sk}$. See [Why Threshold Secret Sharing]. # References From a00606f9c711b2ab03162f9f7c9dfa46965d35c4 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 4 Mar 2026 21:28:46 -0300 Subject: [PATCH 07/56] correct open issues and define N_s as part of spec Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 25 +++--------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 533e0990c..8b473ff5c 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -366,8 +366,9 @@ Proof proves membership without exposing which VC is being opened. ### Vote Share -A vote share is one of the $N_s$ encrypted portions of a voter's ballot -count within a VC. +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. For share index $i \in \{0 \ldots N_s - 1\}$: @@ -1259,26 +1260,6 @@ finalization of this ZIP. # Open issues -- The Poseidon instantiation ($\mathsf{P128Pow5T3}$) and round - constants should be explicitly referenced or pinned once a canonical - parameter set for Zcash usage is published. -- The exact sighash construction for the Delegation Proof and Vote - Proof (what is signed by the spend authorization key, and how it - binds to the proof's public inputs) requires further specification. -- The depth of the Vote Commitment Tree - ($\mathsf{MerkleDepth}^{\mathsf{vct}}$) should be specified based on expected - capacity requirements. -- The encoding of domain separator strings (`"governance authorization"`, - `"vote authority spend"`, `"share spend"`) as field elements should be - pinned to a specific encoding procedure. -- The number of shares $N_s = 16$ is a design target; the current - implementation uses a smaller value. The final parameter should be - confirmed based on circuit size and proving time benchmarks. -- The interaction between delegation (VAN splitting) and the Delegation - Proof circuit (which produces a single VAN) should be specified for - the delegation extension. -- The $\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}$ derivation procedure should be - specified to ensure uniqueness across rounds. - Per-validator Chaum–Pedersen DLEQ proofs for partial decryptions would allow immediate identification of misbehaving validators at tally time, converting the current liveness cost (subset search) to From 943036d80bbd100db408d72f269d0e127248f02d Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 4 Mar 2026 21:44:36 -0300 Subject: [PATCH 08/56] fix threshold Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 8b473ff5c..39cca52c8 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -177,7 +177,7 @@ 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 2n/3 \rceil + 1$) to reconstruct the key and decrypt +$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. @@ -1067,7 +1067,7 @@ holds $\mathsf{ea}\_\mathsf{sk}$; each validator holds only its share. The round transitions to active status once a quorum of validators have verified their shares and acknowledged receipt. -At tally time, at least $t = \lceil 2n/3 \rceil + 1$ validators +At tally time, at least $t = \lceil n/2 \rceil + 1$ validators cooperate to produce partial decryptions of the aggregate ciphertext and post them on-chain. The partial decryptions are combined via Lagrange interpolation — the full secret key is never reconstructed. @@ -1189,10 +1189,10 @@ additional unlinkability. The election authority key is split into Shamir shares rather than distributed intact to all validators. This ensures that compromise of any single validator (or any minority below $t$) does not expose the full decryption key. The threshold -$t = \lceil 2n/3 \rceil + 1$ aligns with the CometBFT supermajority -assumption: an adversary that controls fewer than one-third of -validators cannot reach the decryption threshold, so the existing -consensus trust boundary extends to vote-amount privacy. +$t = \lceil n/2 \rceil + 1$ ensures that an adversary controlling fewer +than half of validators cannot reach the decryption threshold. Under +the standard CometBFT assumption (fewer than one-third Byzantine), +this provides an additional safety margin for vote-amount privacy. The protocol uses a trusted dealer rather than distributed key generation (DKG). Feldman VSS commitments (which would let each From efa58a7181d3525095fdf61bd9fe06f41ef6510e Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 4 Mar 2026 21:47:45 -0300 Subject: [PATCH 09/56] fix category Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 39cca52c8..4647c019a 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -7,7 +7,7 @@ Credits: Daira-Emma Hopwood Jack Grigg Status: Draft - Category: Informational + Category: Standards Created: 2026-03-04 License: MIT Pull-Request: From 8cf6c3f486687caf8d9eb010d9663626cd471319 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 4 Mar 2026 21:58:51 -0300 Subject: [PATCH 10/56] Rationaly on delegation to hotkey Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 4647c019a..fc3b233d3 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -1131,6 +1131,30 @@ bounded discrete-log search at tally time has a smaller search space. The 0.125 ZEC minimum also prevents dust delegations from bloating vote chain state. +## 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 $N_s$ Shares Per Vote Splitting a vote into $N_s$ shares serves two purposes. First, it From c6fa8e7dd7aa1f4d5c2c4bb3e4be5261d208eee6 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 4 Mar 2026 22:37:05 -0300 Subject: [PATCH 11/56] fix off-by-one error in VAN Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index fc3b233d3..48925bfd1 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -724,14 +724,14 @@ producing distinct field elements. ##### New VAN Construction **Condition 6 — Proposal authority decrement.** Bit -$\mathsf{proposal}\_\mathsf{id}$ is cleared in the authority bitmask: +$\mathsf{proposal}\_\mathsf{id} - 1$ is cleared in the authority bitmask: - $\mathsf{proposal}\_{\mathsf{authority}\_\mathsf{old}}$ is decomposed into 16 boolean wires $b_0, \ldots, b_{15}$ that recompose to the original value. -- The bit at position $\mathsf{proposal}\_\mathsf{id}$ is asserted to be 1 +- The bit at position $\mathsf{proposal}\_\mathsf{id} - 1$ is asserted to be 1 (the voter has authority for this proposal). - $\mathsf{proposal}\_{\mathsf{authority}\_\mathsf{new}}$ is the recomposition with bit - $\mathsf{proposal}\_\mathsf{id}$ cleared; all other bits are unchanged. + $\mathsf{proposal}\_\mathsf{id} - 1$ cleared; all other bits are unchanged. **Condition 7 — New VAN integrity.** The new VAN is correctly constructed: From d47d0317d88af0214bcf9ec3b97776bf8fecfc6f Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 4 Mar 2026 22:48:47 -0300 Subject: [PATCH 12/56] rounds of fixes Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 44 ++++++++++++++++++------ 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 48925bfd1..a410e3554 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -4,8 +4,8 @@ Roman Akhtariev Adam Tucker Greg Nagy - Credits: Daira-Emma Hopwood - Jack Grigg + Credits: Daira-Emma Hopwood + Jack Grigg Status: Draft Category: Standards Created: 2026-03-04 @@ -15,9 +15,9 @@ # Terminology -The key words "MUST", "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 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] @@ -435,6 +435,28 @@ Each set is append-only within a voting round. The vote chain rejects any transaction that publishes a nullifier already present in the corresponding set. +### 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. + +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 | + ## Ballot Scaling @@ -595,7 +617,7 @@ authorization signature $\sigma$ MUST perform the following checks: 4. Verify that no $\mathsf{gov}\_{\mathsf{null}\_\mathsf{i}}$ appears in the governance nullifier set. If any does, reject as a double-delegation. 5. Verify that $\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}$ matches an active round. -6. Insert $\mathsf{van}$ and $\mathsf{cmx}\_\mathsf{new}$ into the VCT. +6. Insert $\mathsf{van}$ into the VCT. 7. Add all $\mathsf{gov}\_{\mathsf{null}\_\mathsf{i}}$ to the governance nullifier set. ### Delegation Message @@ -668,9 +690,9 @@ The prover knows: - $\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{1}, \ldots, \mathsf{v}_{N_s}$ — plaintext share values. -- $r_1, \ldots, r_{N_s}$ — El Gamal encryption randomness per share. -- $\mathsf{blind}\_\mathsf{1}, \ldots, \mathsf{blind}_{N_s}$ — per-share blind +- $\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 @@ -745,11 +767,11 @@ randomness; only $\mathsf{proposal}\_\mathsf{authority}$ changes. **Condition 8 — Shares sum correctness.** -$$\sum_{i=1}^{N_s} \mathsf{v}\_\mathsf{i} = \mathsf{num}\_\mathsf{ballots}$$ +$$\sum_{i=0}^{N_s - 1} \mathsf{v}\_\mathsf{i} = \mathsf{num}\_\mathsf{ballots}$$ **Condition 9 — Shares range check.** Each share is bounded: -$$0 \leq \mathsf{v}\_\mathsf{i} < 2^{30} \quad \text{for each } i \in \{1 \ldots N_s\}$$ +$$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 From 401efd70f290ff5fd8cfad43af0107552d6edacb Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 4 Mar 2026 22:57:42 -0300 Subject: [PATCH 13/56] add Keystone / hardware wallet note Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 51 ++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index a410e3554..29d4cffd5 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -212,6 +212,9 @@ see [^pir-governance]. - The protocol supports up to 16 proposals per voting round. - The protocol supports delegation of voting authority to a third-party hotkey. +- The delegation phase is compatible with hardware wallets that support + only the standard Orchard PCZT signing flow, without requiring + firmware changes specific to the voting protocol. # Non-requirements @@ -1177,6 +1180,35 @@ entirely. Delegation preserves the hardware wallet's role as the sole custodian of spending keys while enabling full participation in the voting protocol. +## 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 (e.g., Keystone) that support only the standard Orchard PCZT +signing flow. + +Without voting-specific firmware, the hardware wallet interprets the +delegation as a standard Orchard Action and signs the transaction +sighash accordingly. The dummy note's rho binding ensures this signature +is non-replayable and scoped to the exact delegation context (all note +commitments, the VAN, and the voting round), even though the hardware +wallet is unaware of governance semantics. The sighash that the hardware +wallet signs commits to a structure that appears to be a fund-moving +transaction — this is an inherent consequence of reusing the standard +signing flow and cannot be avoided without firmware changes. + +When hardware wallet firmware adds voting-aware signing (e.g., a +governance network byte analogous to the testnet byte), the firmware can +display the delegation context to the user (notes, amounts, voting +round) and sign a governance-specific sighash that binds directly to +the delegation parameters. The signed note scaffolding — signed note +integrity, signed note nullifier, rho binding, and output note +commitment — can then be removed from the circuit. This migration is +purely subtractive: the simplified circuit is a strict subset of the +current one, and only ZKP #1 (the Delegation Proof) changes. ZKP #2 +(Vote Proof) and ZKP #3 (Vote Reveal Proof) are unaffected. + ## Why $N_s$ Shares Per Vote Splitting a vote into $N_s$ shares serves two purposes. First, it @@ -1292,6 +1324,19 @@ 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. +The protocol is designed to support two hardware wallet modes for the +delegation phase. In the pre-firmware mode, the spend authorization +signature is obtained through a standard PCZT-based signing flow: the +hardware wallet signs what it interprets as an Orchard Action, and the +dummy signed note mechanism (see [Why a Dummy Signed Note]) ensures +correctness. In the post-firmware mode, the hardware wallet recognizes +a governance-specific network byte, displays the delegation context +(notes, amounts, voting round) to the user, and signs a +governance-specific sighash. Both modes produce a valid +$\mathsf{SpendAuthSig}^{\mathsf{Orchard}}$ that the Delegation Proof +consumes. The post-firmware circuit is a strict subset of the +pre-firmware circuit (see [Open issues]). + # Reference implementation @@ -1317,6 +1362,12 @@ finalization of this ZIP. - Distributed key generation (DKG) would eliminate the trusted dealer entirely, producing $\mathsf{ea}\_\mathsf{pk}$ without any single party ever holding $\mathsf{ea}\_\mathsf{sk}$. See [Why Threshold Secret Sharing]. +- Hardware wallet firmware with voting-aware signing (e.g., a + governance network byte) would allow a simplified Delegation Proof + circuit that removes the dummy signed note scaffolding (signed note + integrity, rho binding, output note commitment). The migration is + purely subtractive — the post-firmware circuit is a strict subset of + the pre-firmware circuit. See [Why a Dummy Signed Note]. # References From ee8a7c7e01b3132abe59af888ca2ed8ab9730962 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 4 Mar 2026 23:05:49 -0300 Subject: [PATCH 14/56] avoid definitions in terminology Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 29d4cffd5..471714c85 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -26,9 +26,8 @@ The terms below are to be interpreted as follows: Ballot -: A unit of voting weight equal to - $\lfloor v / 12{,}500{,}000 \rfloor$ where $v$ is a balance in - zatoshi. One ballot corresponds to 0.125 ZEC. +: 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) @@ -51,8 +50,8 @@ Vote Commitment Tree (VCT) Vote share : One of $N_s$ encrypted portions of a voter's ballot count within a - Vote Commitment. Each share is an El Gamal ciphertext under the - election authority's public key. + Vote Commitment. Each share is submitted independently for + homomorphic accumulation. Voting round From 3052b45a85ac96a5e5ea9bee121726b1811b5432 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 4 Mar 2026 23:13:24 -0300 Subject: [PATCH 15/56] cite Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 62 ++++++++++++++++-------- 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 471714c85..93237b1d5 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -212,8 +212,8 @@ see [^pir-governance]. - The protocol supports delegation of voting authority to a third-party hotkey. - The delegation phase is compatible with hardware wallets that support - only the standard Orchard PCZT signing flow, without requiring - firmware changes specific to the voting protocol. + only the standard Orchard PCZT [^pczt] signing flow, without + requiring firmware changes specific to the voting protocol. # Non-requirements @@ -275,8 +275,9 @@ decryptions. ## El Gamal Encryption on Pallas -The protocol uses additively homomorphic El Gamal encryption over the -Pallas curve [^protocol-pallasandvesta] to encrypt vote share amounts. +The protocol uses additively homomorphic El Gamal encryption [^elgamal] +over the Pallas curve [^protocol-pallasandvesta] to encrypt vote share +amounts. Let $G$ be the Pallas $\mathsf{SpendAuthSig}^{\mathsf{Orchard}}$ generator [^protocol-concretespendauthsig] and let @@ -300,9 +301,9 @@ $\mathsf{ea}\_\mathsf{sk}$: $$C_2 - [\mathsf{ea}\_\mathsf{sk}]\, C_1 = [v]\, G$$ -The discrete logarithm $v$ is recovered via baby-step giant-step, which -is feasible because ballot counts are bounded (the total ZEC supply -yields at most $\approx 1.68 \times 10^8$ ballots). +The discrete logarithm $v$ is recovered via baby-step giant-step +[^bsgs], which is feasible because ballot counts are bounded (the total +ZEC supply yields at most $\approx 1.68 \times 10^8$ ballots). ## Data Structures @@ -716,7 +717,7 @@ 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)$. +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 randomized voting public key is a valid rerandomization: @@ -1085,8 +1086,9 @@ per (proposal, decision) pair. The threshold decryption procedure is specified i Each voting round uses a fresh election authority keypair $(\mathsf{ea}\_\mathsf{sk}, \mathsf{ea}\_\mathsf{pk})$ produced by an automated threshold secret sharing ceremony among the vote chain's validator set. A trusted -dealer generates $\mathsf{ea}\_\mathsf{sk}$, splits it into Shamir shares, distributes the shares to eligible validators via -ECIES, and deletes the full key. After the ceremony, no single party +dealer generates $\mathsf{ea}\_\mathsf{sk}$, splits it into Shamir +shares [^shamir], distributes the shares to eligible validators via +ECIES [^ecies], and deletes the full key. After the ceremony, no single party holds $\mathsf{ea}\_\mathsf{sk}$; each validator holds only its share. The round transitions to active status once a quorum of validators have verified their shares and acknowledged receipt. @@ -1096,9 +1098,8 @@ cooperate to produce partial decryptions of the aggregate ciphertext and post them on-chain. The partial decryptions are combined via Lagrange interpolation — the full secret key is never reconstructed. -The ceremony protocol — including dealer selection, Feldman VSS -construction, ECIES share distribution, validator acknowledgment, -confirmation thresholds, timeout and jailing rules, and threshold +The ceremony protocol — including dealer selection, ECIES share distribution, validator +acknowledgment, confirmation thresholds, timeout and jailing rules, and threshold decryption procedures — is specified in [^ea-ceremony]. @@ -1136,11 +1137,12 @@ independent of mainchain upgrade cycles. ## Why Poseidon for the VCT -The Orchard note commitment tree uses Sinsemilla 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 -arithmetic circuits than Sinsemilla (which is optimized for bitstring +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 @@ -1268,11 +1270,11 @@ ensures that compromise of any single validator (or any minority below $t$) does not expose the full decryption key. The threshold $t = \lceil n/2 \rceil + 1$ ensures that an adversary controlling fewer than half of validators cannot reach the decryption threshold. Under -the standard CometBFT assumption (fewer than one-third Byzantine), +the standard CometBFT [^cometbft] assumption (fewer than one-third Byzantine), this provides an additional safety margin for vote-amount privacy. The protocol uses a trusted dealer rather than distributed key -generation (DKG). Feldman VSS commitments (which would let each +generation (DKG). Feldman VSS commitments [^feldman] (which would let each validator verify that its share is consistent with $\mathsf{ea}\_\mathsf{pk}$) are omitted for initial scope; the dealer is trusted to distribute correct shares, and any tampering would be @@ -1388,3 +1390,23 @@ finalization of this ZIP. [^ea-ceremony]: [Draft ZIP: Election Authority Key Ceremony](draft-valargroup-ea-key-ceremony) [^chaum-pedersen]: [Chaum, D. and Pedersen, T.P. "Wallet Databases with Observers." CRYPTO 1992](https://link.springer.com/chapter/10.1007/3-540-48071-4_7) + +[^elgamal]: [T. ElGamal, "A public key cryptosystem and a signature scheme based on discrete logarithms", IEEE Transactions on Information Theory, vol. 31, no. 4, pp. 469-472, 1985](https://doi.org/10.1109/TIT.1985.1057074) + +[^shamir]: [A. Shamir, "How to share a secret", Communications of the ACM, vol. 22, no. 11, pp. 612-613, 1979](https://doi.org/10.1145/359168.359176) + +[^feldman]: [P. Feldman, "A practical scheme for non-interactive verifiable secret sharing", in Proceedings of the 28th IEEE Symposium on Foundations of Computer Science, pp. 427-437, 1987](https://doi.org/10.1109/SFCS.1987.4) + +[^ecies]: [V. Shoup, "A Proposal for an ISO Standard for Public Key Encryption", version 2.1, 2001](https://www.shoup.net/papers/iso-2_1.pdf) + +[^bsgs]: [D. Shanks, "Class number, a theory of factorization, and genera", in Proceedings of Symposia in Pure Mathematics, vol. 20, pp. 415-440, 1971](https://doi.org/10.1090/pspum/020/0316385) + +[^halo2]: [S. Bowe, J. Grigg, and D. Hopwood, "Recursive Proof Composition without a Trusted Setup", 2019](https://eprint.iacr.org/2019/1021) + +[^cometbft]: [CometBFT Specification](https://docs.cometbft.com/v1/spec/) + +[^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) + +[^pczt]: [zcash/zips issue #693: Standardize a protocol for creating shielded transactions offline (PCZT)](https://github.com/zcash/zips/issues/693) From 22944039bbd79f05d7fe301a51980392d93573bf Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 4 Mar 2026 23:22:03 -0300 Subject: [PATCH 16/56] add a note on VAN removal Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 43 ++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 93237b1d5..c682b77f6 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -1310,6 +1310,40 @@ and exclusion of misbehaving validators, reducing verification to $O(1)$ per partial decryption. This is left as a future enhancement (see [Open issues]). +## 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 the alternative removes +partial delegation — the ability to split $\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 mechanism that enables this. 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. + +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 Domain Tags in the VCT Both VANs and VCs are leaves in the same Merkle tree. The domain tags @@ -1369,6 +1403,15 @@ finalization of this ZIP. integrity, rho binding, output note commitment). The migration is purely subtractive — the post-firmware circuit is a strict subset of the pre-firmware circuit. See [Why a Dummy Signed Note]. +- 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 + eliminates 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]. # References From 2390a59434b71143abebe10d74535a9bcd3da69a Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 4 Mar 2026 23:39:26 -0300 Subject: [PATCH 17/56] quantum risk Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 51 ++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index c682b77f6..218bfc092 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -1344,6 +1344,46 @@ 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 + +The protocol uses El Gamal on the Pallas curve, which is vulnerable to a +quantum adversary running Shor's algorithm. A sufficiently powerful +quantum computer could recover $\mathsf{ea}\_\mathsf{sk}$ from +$\mathsf{ea}\_\mathsf{pk}$ and decrypt individual vote share ciphertexts, +breaking vote-amount privacy for any round whose on-chain ciphertexts +were recorded. + +Post-quantum aggregatable encryption — a scheme that is both +quantum-resistant and additively homomorphic — would eliminate this +risk. However, no such scheme is mature enough for production use. +Lattice-based homomorphic encryption exists in theory, but practical +instantiations have ciphertext sizes, proving costs, and threshold +decryption complexities that are orders of magnitude larger than +El Gamal on an elliptic curve. The homomorphic tally +(component-wise point addition of Pallas points) and the threshold +decryption (Shamir/Feldman secret sharing with Lagrange interpolation +over a scalar field) are both simple precisely because El Gamal +operates in the same algebraic setting as the rest of the protocol. +Replacing it would require a fundamentally different threshold +protocol and circuit design for the Vote Proof and Vote Reveal Proof. + +The practical consequence is that vote-amount privacy has a finite +horizon tied to quantum computing timelines. Ciphertexts are stored +on-chain permanently; an adversary who records them today could decrypt +individual share amounts once a cryptographically relevant quantum +computer exists. Voter *identity* is unaffected — alternate nullifier +unlinkability relies on Poseidon preimage resistance, not on El Gamal +— but *how much* a voter allocated to each option would be exposed. + +This tradeoff is accepted for initial deployment. Per-round key rotation +(each round uses a fresh $\mathsf{ea}\_\mathsf{sk}$) limits a classical +compromise to a single round, and vote splitting across $N_s$ shares +means a quantum adversary would recover individual shares rather than +complete ballot allocations unless it also breaks the vote commitment +unlinkability (which depends on the Poseidon-based blinded share +commitments, not on El Gamal). Post-quantum migration is tracked as an +open issue. + ## Why Domain Tags in the VCT Both VANs and VCs are leaves in the same Merkle tree. The domain tags @@ -1412,6 +1452,17 @@ finalization of this ZIP. to track VCT paths (e.g., full server-side path retrieval), this tradeoff should be revisited. See [Why a Send-Based VAN Model]. +- Post-quantum aggregatable encryption would eliminate the long-term + "harvest now, decrypt later" risk to vote-amount privacy. On-chain + El Gamal ciphertexts are permanent; a future quantum adversary could + decrypt individual share amounts for any recorded round. No production- + ready post-quantum scheme currently offers both additive homomorphism + and efficient threshold decryption. If such a scheme matures, the + El Gamal layer (encryption in the Vote Proof, ciphertext verification + in the Vote Reveal Proof, and the homomorphic tally procedure) could + be replaced without changing the commitment, nullifier, or Merkle + membership components of the protocol. + See [Why Classical El Gamal Rather Than Post-Quantum Encryption]. # References From 852b62d1f8671a04c10835f07d4951c2ceebc5cb Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 5 Mar 2026 10:27:47 -0300 Subject: [PATCH 18/56] avoid numbering of ZKPs Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 218bfc092..9cd9d53e4 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -167,7 +167,7 @@ submissions. 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 (ZKP #3) proves +**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 @@ -1026,7 +1026,7 @@ For each payload received, the server: 1. Waits a randomized delay. 2. Obtains the current VCT Merkle path for the VC. 3. Derives the share nullifier. -4. Constructs the Vote Reveal Proof (ZKP #3). +4. Constructs the Vote Reveal Proof. 5. Submits the share reveal transaction to the vote chain. **What the server learns:** the encrypted share ciphertext and blind @@ -1207,8 +1207,8 @@ the delegation parameters. The signed note scaffolding — signed note integrity, signed note nullifier, rho binding, and output note commitment — can then be removed from the circuit. This migration is purely subtractive: the simplified circuit is a strict subset of the -current one, and only ZKP #1 (the Delegation Proof) changes. ZKP #2 -(Vote Proof) and ZKP #3 (Vote Reveal Proof) are unaffected. +current one, and only the Delegation Proof changes. The Vote Proof +and Vote Reveal Proof are unaffected. ## Why $N_s$ Shares Per Vote @@ -1244,7 +1244,7 @@ ciphertexts with their commitments. ## Why Server-Delegated Share Reveal -The Vote Reveal Proof (ZKP #3) is constructed by the submission server +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 From 5ce859b056177e9d0e4d015bb96691c6570558b5 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 5 Mar 2026 10:47:45 -0300 Subject: [PATCH 19/56] clean ups Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 49 +++++++++++++++++------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 9cd9d53e4..ecedc9fb3 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -185,7 +185,7 @@ 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 mitigates this risk. +metadata; using multiple independent servers with randomized delays for submission mitigates this risk. **Non-membership tree queries.** Obtaining exclusion proofs for the nullifier non-membership tree during delegation requires querying a data @@ -206,9 +206,8 @@ see [^pir-governance]. - 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 and verify the election authority's - decryption proof. -- The protocol supports up to 16 proposals per voting round. + homomorphic accumulation. +- The protocol supports up to 15 proposals per voting round. - The protocol supports delegation of voting authority to a third-party hotkey. - The delegation phase is compatible with hardware wallets that support @@ -327,8 +326,10 @@ where: 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. Full - authority for 16 proposals is $2^{16} - 1 = 65535$. + 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. @@ -358,7 +359,7 @@ where: - $\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 16\}$ — which proposal this +- $\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). @@ -664,7 +665,7 @@ Given a primary input: - $\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 16\}$ — which proposal. +- $\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). @@ -749,14 +750,14 @@ producing distinct field elements. ##### New VAN Construction **Condition 6 — Proposal authority decrement.** Bit -$\mathsf{proposal}\_\mathsf{id} - 1$ is cleared in the authority bitmask: +$\mathsf{proposal}\_\mathsf{id}$ is cleared in the authority bitmask: - $\mathsf{proposal}\_{\mathsf{authority}\_\mathsf{old}}$ is decomposed into 16 boolean wires $b_0, \ldots, b_{15}$ that recompose to the original value. -- The bit at position $\mathsf{proposal}\_\mathsf{id} - 1$ is asserted to be 1 +- The bit at position $\mathsf{proposal}\_\mathsf{id}$ is asserted to be 1 (the voter has authority for this proposal). - $\mathsf{proposal}\_{\mathsf{authority}\_\mathsf{new}}$ is the recomposition with bit - $\mathsf{proposal}\_\mathsf{id} - 1$ cleared; all other bits are unchanged. + $\mathsf{proposal}\_\mathsf{id}$ cleared; all other bits are unchanged. **Condition 7 — New VAN integrity.** The new VAN is correctly constructed: @@ -840,7 +841,7 @@ A vote transaction submitted to the vote chain MUST contain: | $\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 16\}$ | Proposal identifier | +| $\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 | @@ -863,7 +864,7 @@ Given a primary input: prevents double-counting. - $\mathsf{enc}\_\mathsf{share} ⦂ \mathbb{P}^* \times \mathbb{P}^*$ — the El Gamal ciphertext $(C_1, C_2)$ for this share. -- $\mathsf{proposal}\_\mathsf{id} ⦂ \{1 \ldots 16\}$ — which proposal. +- $\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. @@ -973,7 +974,7 @@ A share reveal transaction submitted to the vote chain MUST contain: | $\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 16\}$ | Proposal identifier | +| $\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{anchor}\_\mathsf{height}$ | integer | VCT anchor height | @@ -1263,6 +1264,26 @@ 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 The election authority key is split into Shamir shares rather than distributed intact to all validators. This From a1fc02bc1f5cef145ac1a8788b358e94465b1a9c Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 5 Mar 2026 10:58:05 -0300 Subject: [PATCH 20/56] clarify VC reveal Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index ecedc9fb3..9a15e1574 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -365,8 +365,12 @@ where: voter's choice (0-indexed into the proposal's declared options). A VC is created during voting (Phase 2) and opened during share reveal -(Phase 4/5). The VC itself is never revealed on-chain; the Vote Reveal -Proof proves membership without exposing which VC is being opened. +(Phase 4/5). The VC hash is posted on-chain as a public input of the +Vote Proof and inserted into the VCT, but its preimage fields +($\mathsf{shares}\_\mathsf{hash}$ and $\mathsf{vote}\_\mathsf{decision}$) +are private witnesses in that proof. During share reveal, the Vote +Reveal Proof proves membership in the VCT without exposing which VC is +being opened. ### Vote Share From 57484caf69fa704b8c58be5b32f14c3e217e5967 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 5 Mar 2026 11:07:30 -0300 Subject: [PATCH 21/56] VCT capacity Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 39 +++++++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 9a15e1574..1f433a423 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -208,6 +208,8 @@ see [^pir-governance]. - The aggregate tally is publicly verifiable: any party can confirm the homomorphic accumulation. - The protocol supports up to 15 proposals per voting round. +- The Vote Commitment Tree has capacity for at least several million + leaves (combined VANs and VCs) per voting round. - The protocol supports delegation of voting authority to a third-party hotkey. - The delegation phase is compatible with hardware wallets that support @@ -415,11 +417,12 @@ $\mathsf{blind}$ is the blind factor for the revealed share. ### Vote Commitment Tree -The VCT is an append-only Merkle tree of depth -$\mathsf{MerkleDepth}^{\mathsf{vct}}$ that stores both VANs and VCs as leaves. -The tree MUST use the Poseidon hash function [^poseidon] over -the Pallas scalar field for all internal node hashing, with -the same instantiation used by the nullifier non-membership +The VCT is an incremental Merkle tree [^protocol-merkletree] of depth +$\mathsf{MerkleDepth}^{\mathsf{vct}} = 24$ that stores both VANs and VCs as leaves. +It uses the same append-only data structure as the Orchard note +commitment tree, but with Poseidon [^poseidon] over the Pallas +scalar field for internal node hashing instead of Sinsemilla, +using the same instantiation as the nullifier non-membership tree [^balance-proof]. Domain separation between VANs and VCs is achieved structurally: the @@ -1409,6 +1412,22 @@ unlinkability (which depends on the Poseidon-based blinded share commitments, not on El Gamal). Post-quantum migration is tracked as an open issue. +## 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. + +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 @@ -1488,6 +1507,14 @@ finalization of this ZIP. be replaced without changing the commitment, nullifier, or Merkle membership components of the protocol. See [Why Classical El Gamal Rather Than Post-Quantum Encryption]. +- The VCT depth of 24 ($2^{24} \approx 16.7$ million leaves) is + sufficient for current governance usage projections, but may need to + be increased via a vote chain upgrade if participation grows + significantly (e.g., more voters, more proposals per round, or more + frequent rounds reusing the same tree). Increasing the depth requires + updating the Merkle membership circuits in both the Vote Proof and + Vote Reveal Proof, reproving the verification key, and coordinating + a CometBFT chain upgrade. See [Why VCT Depth 24]. # References @@ -1524,6 +1551,8 @@ finalization of this ZIP. [^cometbft]: [CometBFT Specification](https://docs.cometbft.com/v1/spec/) +[^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) From c5daabe6960af9d76705653f1b46c886863336f4 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 5 Mar 2026 11:29:52 -0300 Subject: [PATCH 22/56] clarify partial delegations Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 37 ++++++++++++++++-------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 1f433a423..3ca5b8018 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -344,10 +344,12 @@ where $\mathsf{tag}_{\mathsf{van}}$ is the field-element encoding of the domain separator `"vote authority spend"` and $\mathsf{nk}$ is the nullifier deriving key from the holder's full viewing key. -**Lifecycle.** A VAN is created during delegation (Phase 1), consumed -during voting (Phase 2) or further delegation, and replaced by a new VAN -with updated $\mathsf{proposal}\_\mathsf{authority}$ (voting) or split -$\mathsf{num}\_\mathsf{ballots}$ (delegation). +**Lifecycle.** A VAN is created during delegation (Phase 1) and consumed +during voting (Phase 2), which produces 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) @@ -1354,13 +1356,18 @@ 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 the alternative removes -partial delegation — the ability to split $\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 mechanism that enables this. 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. +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 @@ -1487,11 +1494,17 @@ finalization of this ZIP. integrity, rho binding, output note commitment). The migration is purely subtractive — the post-firmware circuit is a strict subset of the pre-firmware circuit. See [Why a Dummy Signed Note]. +- 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 - eliminates partial delegation and clients still need VCT Merkle path + 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. From 4c0ed07485b9d2d01328db6a066c25d0a5053eed Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 5 Mar 2026 11:47:36 -0300 Subject: [PATCH 23/56] governance hotkey clarification Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 59 ++++++++++++++++++++---- 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 3ca5b8018..be1bee551 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -338,11 +338,11 @@ where: **VAN nullifier.** When a VAN is consumed (to vote or delegate), its nullifier is: -$$\mathsf{van}\_\mathsf{nullifier} = \mathsf{Poseidon}\bigl(\mathsf{nk}, \mathsf{tag}_{\mathsf{van}}, \mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}, \mathsf{van}\bigr)$$ +$$\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{nk}$ is the nullifier -deriving key from the holder's full viewing key. +separator `"vote authority spend"` and $\mathsf{vsk.nk}$ is the nullifier +deriving key from the governance hotkey's full viewing key. **Lifecycle.** A VAN is created during delegation (Phase 1) and consumed during voting (Phase 2), which produces a replacement VAN with updated @@ -471,6 +471,41 @@ The protocol defines three tags: | $\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 + $(\mathsf{vpk}\_{\mathsf{g}\_\mathsf{d}}, \mathsf{vpk}\_{\mathsf{pk}\_\mathsf{d}})$ + at a chosen diversifier index, where + $\mathsf{vpk}\_{\mathsf{pk}\_\mathsf{d}} = [\mathsf{ivk}\_\mathsf{v}]\, \mathsf{vpk}\_{\mathsf{g}\_\mathsf{d}}$ + and $\mathsf{ivk}\_\mathsf{v} = \mathsf{CommitIvk}\_{\mathsf{rivk}\_\mathsf{v}}\!\bigl(\mathsf{Extract}\_{\mathbb{P}}(\mathsf{vsk.ak}),\; \mathsf{vsk.nk}\bigr)$. + +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 MAY be generated deterministically from a seed (e.g., via +Blake2b with a voting-specific personalization) or sampled randomly. +The generation method is an application concern; the protocol requires +only that the resulting key material satisfies the standard Orchard +key relationships above. + + ## Ballot Scaling Orchard note values are denominated in zatoshi. To reduce the bit-width @@ -687,7 +722,7 @@ The prover knows: - $\mathsf{vsk.ak} ⦂ \mathbb{P}^*$ — voting spend authorization validating key. - $\mathsf{vsk.nk} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ — nullifier - deriving key (same as $\mathsf{nk}$ from the holder's FVK). + 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. @@ -748,12 +783,18 @@ where $\mathsf{tag}_{\mathsf{van}}$ is the field-element encoding of ### Rationale for VAN nullifier domain separation -The VAN nullifier and governance nullifier both use $\mathsf{nk}$ as the -Poseidon key (since $\mathsf{vsk.nk}$ and $\mathsf{nk}$ are the same -field element). Cross-circuit collision resistance relies on the domain -tags being distinct: `"vote authority spend"` and +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. +producing distinct field elements. The domain tags provide +defense-in-depth in both cases. ##### New VAN Construction From f369349052e18885fb83fb48982f7a566624ca61 Mon Sep 17 00:00:00 2001 From: Greg Nagy Date: Thu, 5 Mar 2026 19:03:53 +0100 Subject: [PATCH 24/56] VAN commitment: specify two-layer Poseidon structure The VAN commitment formula was written as a single 7-input Poseidon hash, but the implementation uses two separate invocations: ConstantLength<6> for the structural fields, then ConstantLength<2> to fold in the commitment randomness. These produce different outputs because each layer finalizes with its own padding before the next begins. Split the formula into two steps in all four places it appears: the VAN data structure definition, VAN integrity in the Delegation Proof, and old/new VAN integrity in the Vote Proof. --- zips/draft-valargroup-shielded-voting.md | 34 ++++++++++++++++++------ 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index be1bee551..f65c6ef85 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -312,9 +312,17 @@ ZEC supply yields at most $\approx 1.68 \times 10^8$ ballots). ### Vote Authority Note (VAN) A VAN represents spendable voting authority on the vote chain. Its -commitment is: +commitment is computed in two layers: -$$\mathsf{van} = \mathsf{Poseidon}\bigl(\mathsf{DOMAIN\_VAN}, \mathsf{vpk}_{\mathsf{g\_d}}, \mathsf{vpk}_{\mathsf{pk\_d}}, \mathsf{num\_ballots}, \mathsf{voting}_{\mathsf{round\_id}}, \mathsf{proposal\_authority}, \mathsf{gov}_{\mathsf{comm\_rand}}\bigr)$$ +$$\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: @@ -643,9 +651,13 @@ constructed note commitment to an output note addressed to the governance hotkey. **VAN integrity.** The public VAN commitment matches the claimed -governance hotkey, ballot count, round, and full proposal authority: +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{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}}, \mathsf{gov}\_{\mathsf{comm}\_\mathsf{rand}}\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$. @@ -753,9 +765,12 @@ depth $\mathsf{MerkleDepth}^{\mathsf{vct}}$ from $\mathsf{van}_{\mathsf{old}}$ t anchor $\mathsf{rt}^{\mathsf{vct}}$, using Poseidon for internal node hashing. **Condition 2 — Old VAN integrity.** The old VAN commitment matches the -claimed fields: +claimed fields (using the two-layer construction defined in +[Vote Authority Note (VAN)]): -$$\mathsf{van}_{\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}}, \mathsf{gov}\_{\mathsf{comm}\_\mathsf{rand}}\bigr)$$ +$$\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 VAN's address belongs to the voting key: @@ -810,9 +825,12 @@ $\mathsf{proposal}\_\mathsf{id}$ is cleared in the authority bitmask: $\mathsf{proposal}\_\mathsf{id}$ cleared; all other bits are unchanged. **Condition 7 — New VAN integrity.** The new VAN is correctly -constructed: +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{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}}, \mathsf{gov}\_{\mathsf{comm}\_\mathsf{rand}}\bigr)$$ +$$\mathsf{van}\_\mathsf{new} = \mathsf{Poseidon}\bigl(\mathsf{van}\_{\mathsf{core}\_\mathsf{new}}, \mathsf{gov}\_{\mathsf{comm}\_\mathsf{rand}}\bigr)$$ The new VAN reuses the old VAN's diversified address and commitment randomness; only $\mathsf{proposal}\_\mathsf{authority}$ changes. From b576eba3a45cdcb6e43044f207c2d334fc0ab68b Mon Sep 17 00:00:00 2001 From: Greg Nagy Date: Thu, 5 Mar 2026 19:06:05 +0100 Subject: [PATCH 25/56] Remove anchor_height from Vote Reveal Proof MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Share Reveal circuit has 7 public inputs with no anchor_height — only the Vote Proof (ZKP #2) includes it. The tree root alone is sufficient to anchor to a specific tree state. Removed from three places: the Vote Reveal Proof public inputs list, the out-of-circuit verification step, and the Share Reveal Message table. --- zips/draft-valargroup-shielded-voting.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index f65c6ef85..9f6470cc3 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -936,7 +936,6 @@ Given a primary input: - $\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{anchor}\_\mathsf{height} ⦂ \mathbb{N}$ — VCT snapshot height. - $\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ #### Auxiliary Inputs @@ -1021,8 +1020,7 @@ 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 at - $\mathsf{anchor}\_\mathsf{height}$. +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. Add $\mathsf{share}\_\mathsf{nullifier}$ to the share nullifier set. @@ -1045,7 +1043,6 @@ A share reveal transaction submitted to the vote chain MUST contain: | $\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{anchor}\_\mathsf{height}$ | integer | VCT anchor height | | $\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}$ | Pallas scalar | Round identifier | Note: the Vote Reveal Proof has no spend authorization signature From f6d65d096dd2a445a43fe737e985faff06d4ef89 Mon Sep 17 00:00:00 2001 From: Greg Nagy Date: Thu, 5 Mar 2026 19:06:43 +0100 Subject: [PATCH 26/56] Vote Reveal: enc_share public inputs are x-coordinates, not full points The circuit constrains only the x-coordinates of the El Gamal ciphertext (two field elements), not full curve points. The full points are carried in the share reveal message for homomorphic accumulation during tally, but the ZKP only needs x-coordinates for the Poseidon-based share commitment binding. Using full points as public inputs would double the instance cells for no security gain, and an implementer following the old spec literally would produce an incompatible circuit. --- zips/draft-valargroup-shielded-voting.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 9f6470cc3..00771cf2f 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -930,8 +930,10 @@ Given a primary input: - $\mathsf{share}\_\mathsf{nullifier} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ — prevents double-counting. -- $\mathsf{enc}\_\mathsf{share} ⦂ \mathbb{P}^* \times \mathbb{P}^*$ — the - El Gamal ciphertext $(C_1, C_2)$ for this share. +- $C_{1,x}, C_{2,x} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ — the $x$-coordinates + of the El Gamal ciphertext $(C_1, C_2)$ for this share. The full + points are carried in the share reveal message for homomorphic + accumulation, but only the $x$-coordinates are circuit public inputs. - $\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 @@ -985,7 +987,7 @@ inside the VC (via condition 2). The share commitments are blinded ciphertexts or blind factors of other shares to the prover. **Condition 4 — Share membership.** The commitment derived from the -public $\mathsf{enc}\_\mathsf{share} = (C_1, C_2)$ and the witness blind +public $x$-coordinates $C_{1,x}, C_{2,x}$ and the witness blind factor matches the share commitment at position $\mathsf{share}\_\mathsf{index}$: From bd306a1991101aded4a9c0ea78434d423936c952 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 5 Mar 2026 16:17:27 -0300 Subject: [PATCH 27/56] clarify what DKG is used for Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 00771cf2f..94b8c1175 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -227,7 +227,8 @@ see [^pir-governance]. - The operational process for conducting a coinholder vote (validator setup, poll creation, deadlines) is out of scope; it is expected to be specified in a separate ZIP. -- Distributed key generation (producing $\mathsf{ea}\_\mathsf{pk}$ without any +- Distributed key generation of the election authority's El Gamal keypair + among validators (producing $\mathsf{ea}\_\mathsf{pk}$ without any single party constructing $\mathsf{ea}\_\mathsf{sk}$) is out of scope; the current design uses a trusted dealer. - Post-quantum security of the El Gamal encryption layer is out of From 8a444c56747835f685d68ddb21abe97162c4919f Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 5 Mar 2026 16:41:09 -0300 Subject: [PATCH 28/56] minor style consistency edits Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 139 +++++++++++------------ 1 file changed, 69 insertions(+), 70 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 94b8c1175..7bad9f187 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -104,7 +104,7 @@ 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 +reveal proof*, constructed by an untrusted submission server, opens individual encrypted shares for homomorphic accumulation, without revealing which Vote Commitment the share originated from. @@ -125,7 +125,7 @@ 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. +primitive, proving note ownership without revealing standard nullifiers. This ZIP builds on that primitive to specify a complete voting protocol with the following properties: @@ -243,30 +243,30 @@ see [^pir-governance]. The protocol proceeds in five phases within a voting round. -**Phase 1 — Delegation.** A holder proves ownership of unspent Orchard +**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 +**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 +**Phase 3: Share submission.** The voter sends each encrypted share as an independent payload to one or more submission servers. Each payload contains the data necessary for the server to construct a Vote Reveal Proof. -**Phase 4 — Share reveal.** Each submission server constructs a Vote +**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 a randomized delay. The chain accumulates the revealed El Gamal ciphertexts homomorphically. -**Phase 5 — Tally.** After the voting window closes, at least $t$ +**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 bounded @@ -448,10 +448,10 @@ inserts one VAN; a vote transaction inserts both a new VAN and a VC. The vote chain maintains three disjoint nullifier sets: -1. **Governance nullifiers** — prevent double-delegation of mainchain +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. +2. **VAN nullifiers**: prevent double-spending of voting authority. +3. **Share nullifiers**: prevent double-counting of revealed shares. Each set is append-only within a voting round. The vote chain rejects any transaction that publishes a nullifier already present in the @@ -503,7 +503,7 @@ A governance hotkey consists of: and $\mathsf{ivk}\_\mathsf{v} = \mathsf{CommitIvk}\_{\mathsf{rivk}\_\mathsf{v}}\!\bigl(\mathsf{Extract}\_{\mathbb{P}}(\mathsf{vsk.ak}),\; \mathsf{vsk.nk}\bigr)$. The Delegation Proof does not constrain the hotkey address to match the -holder's key — the output address is bound to the delegation +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. @@ -535,7 +535,7 @@ constrains: 3. $\mathsf{num}\_\mathsf{ballots} \geq 1$ and $\mathsf{num}\_\mathsf{ballots} \leq 2^{30}$ The 30-bit upper bound on $\mathsf{num}\_\mathsf{ballots}$ accommodates up to -$\approx 134$ million ZEC — well above the 21 million ZEC supply cap. +$\approx 134$ million ZEC, well above the 21 million ZEC supply cap. The minimum of 1 ballot prevents dust delegations (holdings below 0.125 ZEC) from producing voting authority. @@ -760,12 +760,12 @@ The prover knows: ##### VAN Ownership and Spending -**Condition 1 — Merkle tree membership.** The old VAN exists in the VCT: +**Condition 1: Merkle tree membership.** The old VAN exists in the VCT: $(\mathsf{path}^{\mathsf{vct}}, \mathsf{pos}^{\mathsf{vct}})$ is 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 old VAN commitment matches the +**Condition 2: Old VAN integrity.** The old VAN commitment matches the claimed fields (using the two-layer construction defined in [Vote Authority Note (VAN)]): @@ -773,19 +773,19 @@ $$\mathsf{van}\_{\mathsf{core}\_\mathsf{old}} = \mathsf{Poseidon}\bigl(\mathsf{D $$\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 VAN's address +**Condition 3: Diversified address integrity.** 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 randomized voting public key is +**Condition 4: Spend authority.** The randomized voting public key is a valid rerandomization: $$\mathsf{r}\_\mathsf{vpk} = \mathsf{vsk.ak} + [\alpha_v]\, G$$ -**Condition 5 — VAN nullifier.** The public $\mathsf{van}\_\mathsf{nullifier}$ +**Condition 5: VAN nullifier.** 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)$$ @@ -815,7 +815,7 @@ defense-in-depth in both cases. ##### New VAN Construction -**Condition 6 — Proposal authority decrement.** Bit +**Condition 6: Proposal authority decrement.** Bit $\mathsf{proposal}\_\mathsf{id}$ is cleared in the authority bitmask: - $\mathsf{proposal}\_{\mathsf{authority}\_\mathsf{old}}$ is decomposed into 16 boolean @@ -825,7 +825,7 @@ $\mathsf{proposal}\_\mathsf{id}$ is cleared in the authority bitmask: - $\mathsf{proposal}\_{\mathsf{authority}\_\mathsf{new}}$ is the recomposition with bit $\mathsf{proposal}\_\mathsf{id}$ cleared; all other bits are unchanged. -**Condition 7 — New VAN integrity.** The new VAN is correctly +**Condition 7: New VAN integrity.** The new VAN is correctly constructed (using the two-layer construction defined in [Vote Authority Note (VAN)]): @@ -838,11 +838,11 @@ randomness; only $\mathsf{proposal}\_\mathsf{authority}$ changes. ##### Vote Commitment Construction -**Condition 8 — Shares sum correctness.** +**Condition 8: Shares sum correctness.** $$\sum_{i=0}^{N_s - 1} \mathsf{v}\_\mathsf{i} = \mathsf{num}\_\mathsf{ballots}$$ -**Condition 9 — Shares range check.** Each share is bounded: +**Condition 9: Shares range check.** Each share is bounded: $$0 \leq \mathsf{v}\_\mathsf{i} < 2^{30} \quad \text{for each } i \in \{0 \ldots N_s - 1\}$$ @@ -851,14 +851,14 @@ 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 baby-step giant-step recovery at tally time. -**Condition 10 — Shares hash integrity.** The blinded share commitments +**Condition 10: Shares hash integrity.** 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}\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.** Each ciphertext is a +**Condition 11: El Gamal encryption integrity.** Each ciphertext is a valid encryption of its share under $\mathsf{ea}\_\mathsf{pk}$: $$C_{1,i} = [r_i]\, G$$ @@ -869,7 +869,7 @@ and witnessed ciphertext points. Constraining only $x$-coordinates is sufficient because the shared randomness $r_i$ binds both curve points, leaving no prover freedom in the $y$-coordinates. -**Condition 12 — Vote commitment integrity.** The public vote commitment +**Condition 12: Vote commitment integrity.** 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)$$ @@ -921,7 +921,7 @@ A vote transaction submitted to the vote chain MUST contain: 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 +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. @@ -961,12 +961,12 @@ The prover (submission server) knows: ##### Vote Commitment Membership -**Condition 1 — Merkle tree membership.** The VC exists in the VCT: +**Condition 1: Merkle tree membership.** The VC exists in the VCT: $(\mathsf{path}^{\mathsf{vct}}, \mathsf{pos}^{\mathsf{vct}})$ is 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 VC is correctly +**Condition 2: Vote commitment integrity.** 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)$$ @@ -977,7 +977,7 @@ revealed share is attributed to the correct proposal and decision. ##### Share Opening -**Condition 3 — Shares hash integrity.** The $\mathsf{shares}\_\mathsf{hash}$ is +**Condition 3: Shares hash integrity.** The $\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)$$ @@ -987,7 +987,7 @@ 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 commitment derived from the +**Condition 4: Share membership.** The commitment derived from the public $x$-coordinates $C_{1,x}, C_{2,x}$ and the witness blind factor matches the share commitment at position $\mathsf{share}\_\mathsf{index}$: @@ -1005,7 +1005,7 @@ or blind factors. ##### Nullifier -**Condition 5 — Share nullifier.** The public +**Condition 5: Share nullifier.** 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)$$ @@ -1117,7 +1117,7 @@ parameters is deferred to the operational voting process ZIP. After the voting window closes, the tally proceeds in three steps. -**Step 1 — Public aggregation.** For each +**Step 1: Public aggregation.** For each $(\mathsf{proposal}\_\mathsf{id}, \mathsf{vote}\_\mathsf{decision})$ pair, the aggregate ciphertext is the component-wise sum of all revealed shares attributed to that pair: @@ -1127,7 +1127,7 @@ $$\mathsf{agg} = \Bigl(\sum C_{1,j}, \sum C_{2,j}\Bigr)$$ This is publicly computable from on-chain data. Any party can independently verify the aggregation. -**Step 2 — Threshold decryption.** At least $t$ validators each produce +**Step 2: Threshold decryption.** At least $t$ validators each produce a partial decryption of the aggregate ciphertext using their Shamir share and post it on-chain. The partial decryptions are combined via Lagrange interpolation: @@ -1141,12 +1141,12 @@ via baby-step giant-step discrete-log search (feasible because the total is bounded by ZEC supply). No party reconstructs $\mathsf{ea}\_\mathsf{sk}$ during this process. -**Step 3 — Public verification.** The individual partial decryptions +**Step 3: Public verification.** The individual partial decryptions $D_i$ and the aggregate ciphertext are all on-chain. Anyone can recompute the Lagrange combination for any qualifying subset of $t$ validators and confirm the claimed $\mathsf{total}\_\mathsf{ballots}$. -Individual vote amounts are never revealed — only the aggregate total +Individual vote amounts are never revealed; only the aggregate total per (proposal, decision) pair. The threshold decryption procedure is specified in [^ea-ceremony]. @@ -1165,11 +1165,11 @@ their shares and acknowledged receipt. At tally time, at least $t = \lceil n/2 \rceil + 1$ validators cooperate to produce partial decryptions of the aggregate ciphertext and post them on-chain. The partial decryptions are combined via -Lagrange interpolation — the full secret key is never reconstructed. +Lagrange interpolation - the full secret key is never reconstructed. -The ceremony protocol — including dealer selection, ECIES share distribution, validator +The ceremony protocol, including dealer selection, ECIES share distribution, validator acknowledgment, confirmation thresholds, timeout and jailing rules, and threshold -decryption procedures — is specified in [^ea-ceremony]. +decryption procedures, is specified in [^ea-ceremony]. ## Vote Chain @@ -1177,16 +1177,16 @@ decryption procedures — is specified in [^ea-ceremony]. The vote chain is a purpose-built chain that records all governance transactions. It maintains: -- The **Vote Commitment Tree** — an append-only Poseidon Merkle tree +- The **Vote Commitment Tree**: an append-only Poseidon Merkle tree storing VANs and VCs. -- Three **nullifier sets** — governance, VAN, and share nullifiers. -- An **encrypted share accumulator** — per +- Three **nullifier sets**: governance, VAN, and share nullifiers. +- An **encrypted share accumulator**: per $(\mathsf{proposal}\_\mathsf{id}, \mathsf{vote}\_\mathsf{decision})$, the running component-wise sum of revealed El Gamal ciphertexts. For each transaction type, the chain verifies the corresponding proof, checks nullifier freshness, updates the VCT, and (for share reveals) -accumulates the ciphertext. All verification is ZKP-based — the chain +accumulates the ciphertext. All verification is ZKP-based - the chain never observes plaintext vote amounts or voter identities. The vote chain's consensus mechanism, block structure, transaction @@ -1209,7 +1209,7 @@ independent of mainchain upgrade cycles. 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 +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- @@ -1229,8 +1229,7 @@ chain state. ## 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 +(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. @@ -1239,13 +1238,13 @@ 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 +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 +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. @@ -1265,16 +1264,16 @@ is non-replayable and scoped to the exact delegation context (all note commitments, the VAN, and the voting round), even though the hardware wallet is unaware of governance semantics. The sighash that the hardware wallet signs commits to a structure that appears to be a fund-moving -transaction — this is an inherent consequence of reusing the standard +transaction - this is an inherent consequence of reusing the standard signing flow and cannot be avoided without firmware changes. When hardware wallet firmware adds voting-aware signing (e.g., a governance network byte analogous to the testnet byte), the firmware can display the delegation context to the user (notes, amounts, voting round) and sign a governance-specific sighash that binds directly to -the delegation parameters. The signed note scaffolding — signed note +the delegation parameters. The signed note scaffolding (signed note integrity, signed note nullifier, rho binding, and output note -commitment — can then be removed from the circuit. This migration is +commitment) can then be removed from the circuit. This migration is purely subtractive: the simplified circuit is a strict subset of the current one, and only the Delegation Proof changes. The Vote Proof and Vote Reveal Proof are unaffected. @@ -1317,7 +1316,7 @@ 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 +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 @@ -1327,7 +1326,7 @@ 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 +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. @@ -1342,7 +1341,7 @@ 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 +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 @@ -1403,7 +1402,7 @@ $O(1)$ per partial decryption. This is left as a future enhancement 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 +"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 @@ -1416,7 +1415,7 @@ 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 +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 @@ -1434,8 +1433,8 @@ 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 +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 @@ -1447,8 +1446,8 @@ $\mathsf{ea}\_\mathsf{pk}$ and decrypt individual vote share ciphertexts, breaking vote-amount privacy for any round whose on-chain ciphertexts were recorded. -Post-quantum aggregatable encryption — a scheme that is both -quantum-resistant and additively homomorphic — would eliminate this +Post-quantum aggregatable encryption, a scheme that is both +quantum-resistant and additively homomorphic, would eliminate this risk. However, no such scheme is mature enough for production use. Lattice-based homomorphic encryption exists in theory, but practical instantiations have ciphertext sizes, proving costs, and threshold @@ -1465,9 +1464,9 @@ The practical consequence is that vote-amount privacy has a finite horizon tied to quantum computing timelines. Ciphertexts are stored on-chain permanently; an adversary who records them today could decrypt individual share amounts once a cryptographically relevant quantum -computer exists. Voter *identity* is unaffected — alternate nullifier -unlinkability relies on Poseidon preimage resistance, not on El Gamal -— but *how much* a voter allocated to each option would be exposed. +computer exists. Voter *identity* is unaffected (alternate nullifier +unlinkability relies on Poseidon preimage resistance, not on El Gamal), +but *how much* a voter allocated to each option would be exposed. This tradeoff is accepted for initial deployment. Per-round key rotation (each round uses a fresh $\mathsf{ea}\_\mathsf{sk}$) limits a classical @@ -1484,7 +1483,7 @@ 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}$ +produce roughly 1 million leaves, well within the $2^{24}$ (~16.7 million) capacity of a depth-24 tree. The reduced depth saves constraint rows in every circuit that performs @@ -1551,16 +1550,16 @@ finalization of this ZIP. governance network byte) would allow a simplified Delegation Proof circuit that removes the dummy signed note scaffolding (signed note integrity, rho binding, output note commitment). The migration is - purely subtractive — the post-firmware circuit is a strict subset of + purely subtractive: the post-firmware circuit is a strict subset of the pre-firmware circuit. See [Why a Dummy Signed Note]. -- 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 +- 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 +- 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 From 39306194d75f59bfd3a82973705e8a42dbe3d9ab Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 5 Mar 2026 19:35:58 -0300 Subject: [PATCH 29/56] update links Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 7bad9f187..17f16084e 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -10,7 +10,7 @@ Category: Standards Created: 2026-03-04 License: MIT - Pull-Request: + Pull-Request: # Terminology @@ -1600,11 +1600,11 @@ finalization of this ZIP. [^poseidon]: [Poseidon: A New Hash Function for Zero-Knowledge Proof Systems](https://eprint.iacr.org/2019/458) -[^balance-proof]: [Draft ZIP: Orchard Proof-of-Balance](draft-valargroup-orchard-balance-proof) +[^balance-proof]: [Orchard Proof-of-Balance](draft-valargroup-orchard-balance-proof) -[^pir-governance]: [Draft ZIP: Private Information Retrieval for Nullifier Exclusion Proofs](draft-valargroup-nullifier-pir) +[^pir-governance]: [Private Information Retrieval for Nullifier Exclusion Proofs](draft-valargroup-nullifier-pir) -[^ea-ceremony]: [Draft ZIP: Election Authority Key Ceremony](draft-valargroup-ea-key-ceremony) +[^ea-ceremony]: [Election Authority Key Ceremony](draft-valargroup-ea-key-ceremony) [^chaum-pedersen]: [Chaum, D. and Pedersen, T.P. "Wallet Databases with Observers." CRYPTO 1992](https://link.springer.com/chapter/10.1007/3-540-48071-4_7) From 8bc3c99120ccc0ea4942a2d01857d6916e90b9ae Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 5 Mar 2026 19:49:41 -0300 Subject: [PATCH 30/56] reference the ea-ceremony ZIP instead of duplicating Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 231 +++++------------------ 1 file changed, 42 insertions(+), 189 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 17f16084e..831d48ff0 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -277,35 +277,16 @@ decryptions. ## El Gamal Encryption on Pallas -The protocol uses additively homomorphic El Gamal encryption [^elgamal] -over the Pallas curve [^protocol-pallasandvesta] to encrypt vote share -amounts. - -Let $G$ be the Pallas $\mathsf{SpendAuthSig}^{\mathsf{Orchard}}$ -generator [^protocol-concretespendauthsig] and let -$\mathsf{ea}\_\mathsf{pk} = [\mathsf{ea}\_\mathsf{sk}]\, G$ be the election authority's -public key for the current voting round. - -To encrypt a ballot count $v$ with randomness $r$: - -$$\mathsf{Enc}(v, r) = \bigl([r]\, G, [v]\, G + [r]\, \mathsf{ea}\_\mathsf{pk}\bigr)$$ - -The ciphertext is a pair of Pallas points $(C_1, C_2)$. - -**Homomorphic property.** Given ciphertexts $\mathsf{Enc}(a, r_1)$ and -$\mathsf{Enc}(b, r_2)$, component-wise point addition yields a valid -encryption of $a + b$: - -$$\mathsf{Enc}(a, r_1) + \mathsf{Enc}(b, r_2) = \mathsf{Enc}(a + b, r_1 + r_2)$$ - -**Decryption.** Given ciphertext $(C_1, C_2)$ and secret key -$\mathsf{ea}\_\mathsf{sk}$: - -$$C_2 - [\mathsf{ea}\_\mathsf{sk}]\, C_1 = [v]\, G$$ - -The discrete logarithm $v$ is recovered via baby-step giant-step -[^bsgs], which is feasible because ballot counts are bounded (the total -ZEC supply yields at most $\approx 1.68 \times 10^8$ ballots). +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 @@ -1115,7 +1096,7 @@ parameters is deferred to the operational voting process ZIP. ## Homomorphic Tally -After the voting window closes, the tally proceeds in three steps. +After the voting window closes, the tally proceeds in two steps. **Step 1: Public aggregation.** For each $(\mathsf{proposal}\_\mathsf{id}, \mathsf{vote}\_\mathsf{decision})$ pair, the aggregate @@ -1127,49 +1108,24 @@ $$\mathsf{agg} = \Bigl(\sum C_{1,j}, \sum C_{2,j}\Bigr)$$ This is publicly computable from on-chain data. Any party can independently verify the aggregation. -**Step 2: Threshold decryption.** At least $t$ validators each produce -a partial decryption of the aggregate ciphertext using their Shamir -share and post it on-chain. The partial decryptions are combined via -Lagrange interpolation: - -$$\sum C_{2,j} - \sum_{i \in S} [\lambda_i]\, D_i = [\mathsf{total}\_\mathsf{ballots}]\, G$$ - -where $D_i = [s_i]\, \sum C_{1,j}$ is validator $V_i$'s partial -decryption and $\lambda_i$ are the Lagrange coefficients for the -participating set $S$. The value $\mathsf{total}\_\mathsf{ballots}$ is recovered -via baby-step giant-step discrete-log search (feasible because the total -is bounded by ZEC supply). No party reconstructs $\mathsf{ea}\_\mathsf{sk}$ -during this process. - -**Step 3: Public verification.** The individual partial decryptions -$D_i$ and the aggregate ciphertext are all on-chain. Anyone can -recompute the Lagrange combination for any qualifying subset of $t$ -validators and confirm the claimed $\mathsf{total}\_\mathsf{ballots}$. - -Individual vote amounts are never revealed; only the aggregate total -per (proposal, decision) pair. The threshold decryption procedure is specified in [^ea-ceremony]. +**Step 2: Threshold decryption and public verification.** At least $t$ +validators produce partial decryptions that are combined via Lagrange +interpolation to recover $\mathsf{total}\_\mathsf{ballots}$. The procedure, +including partial decryption submission, Lagrange combination, and +public verification, is specified in [^ea-ceremony]. Individual vote +amounts are never revealed; only the aggregate total per +(proposal, decision) pair. ## Election Authority Key Each voting round uses a fresh election authority keypair -$(\mathsf{ea}\_\mathsf{sk}, \mathsf{ea}\_\mathsf{pk})$ produced by an automated threshold -secret sharing ceremony among the vote chain's validator set. A trusted -dealer generates $\mathsf{ea}\_\mathsf{sk}$, splits it into Shamir -shares [^shamir], distributes the shares to eligible validators via -ECIES [^ecies], and deletes the full key. After the ceremony, no single party -holds $\mathsf{ea}\_\mathsf{sk}$; each validator holds only its share. The round -transitions to active status once a quorum of validators have verified -their shares and acknowledged receipt. - -At tally time, at least $t = \lceil n/2 \rceil + 1$ validators -cooperate to produce partial decryptions of the aggregate ciphertext -and post them on-chain. The partial decryptions are combined via -Lagrange interpolation - the full secret key is never reconstructed. - -The ceremony protocol, including dealer selection, ECIES share distribution, validator -acknowledgment, confirmation thresholds, timeout and jailing rules, and threshold -decryption procedures, is specified in [^ea-ceremony]. +$(\mathsf{ea}\_\mathsf{sk}, \mathsf{ea}\_\mathsf{pk})$ produced by the key +ceremony specified in [^ea-ceremony]. After the ceremony, no single +party holds $\mathsf{ea}\_\mathsf{sk}$; each validator holds only a Shamir +share. At tally time, at least $t = \lceil n/2 \rceil + 1$ validators +cooperate to produce partial decryptions without reconstructing the +full secret key. ## Vote Chain @@ -1351,52 +1307,12 @@ authority check is intended. An additional non-zero gate provides defense-in-depth by rejecting $\mathsf{proposal}\_\mathsf{id} = 0$ on active rows. -## Why Threshold Secret Sharing - -The election authority key is split into Shamir shares rather than distributed intact to all validators. This -ensures that compromise of any single validator (or any minority below -$t$) does not expose the full decryption key. The threshold -$t = \lceil n/2 \rceil + 1$ ensures that an adversary controlling fewer -than half of validators cannot reach the decryption threshold. Under -the standard CometBFT [^cometbft] assumption (fewer than one-third Byzantine), -this provides an additional safety margin for vote-amount privacy. - -The protocol uses a trusted dealer rather than distributed key -generation (DKG). Feldman VSS commitments [^feldman] (which would let each -validator verify that its share is consistent with -$\mathsf{ea}\_\mathsf{pk}$) are omitted for initial scope; the dealer is -trusted to distribute correct shares, and any tampering would be -caught at tally time. Distributed key generation is a potential future -enhancement (see [Open issues]). - -## Why No Per-Validator DLEQ Proofs - -Each validator's partial decryption $D_i = [s_i]\, \sum C_{1,j}$ is -posted on-chain without a Chaum–Pedersen DLEQ proof [^chaum-pedersen] -attesting that $s_i$ matches the validator's committed share. This is a -deliberate simplification. - -Without DLEQ proofs, a malicious validator can post a bogus $D_i$. Any -Lagrange combination that includes this $D_i$ will produce an incorrect -message point, causing the baby-step giant-step search to fail or -return an implausible result. However, a bogus $D_i$ cannot cause a -wrong tally to be accepted: the decrypted result is publicly -verifiable (anyone can check that the claimed -$\mathsf{total}\_\mathsf{ballots}$ satisfies the decryption equation for -the on-chain aggregate ciphertext), so a tally derived from a corrupted -subset will always be detected. - -The missing DLEQ proofs are therefore a liveness issue, not a -correctness one. A single malicious validator can force the tally -procedure to try alternative subsets of size $t$ to find a fully honest -quorum, delaying finalization. Under the CometBFT assumption that fewer -than one-third of validators are Byzantine, such an honest quorum -always exists. - -Adding per-validator DLEQ proofs would allow immediate identification -and exclusion of misbehaving validators, reducing verification to -$O(1)$ per partial decryption. This is left as a future enhancement -(see [Open issues]). +## Why Threshold Secret Sharing and No Per-Validator DLEQ Proofs + +See [^ea-ceremony] for the rationale behind threshold secret sharing +(including the trusted dealer model and the omission of Feldman VSS +commitments) and the omission of per-validator DLEQ proofs for partial +decryptions. ## Why a Send-Based VAN Model @@ -1439,43 +1355,15 @@ would shift in favor of removing the VAN. See [Open issues]. ## Why Classical El Gamal Rather Than Post-Quantum Encryption -The protocol uses El Gamal on the Pallas curve, which is vulnerable to a -quantum adversary running Shor's algorithm. A sufficiently powerful -quantum computer could recover $\mathsf{ea}\_\mathsf{sk}$ from -$\mathsf{ea}\_\mathsf{pk}$ and decrypt individual vote share ciphertexts, -breaking vote-amount privacy for any round whose on-chain ciphertexts -were recorded. - -Post-quantum aggregatable encryption, a scheme that is both -quantum-resistant and additively homomorphic, would eliminate this -risk. However, no such scheme is mature enough for production use. -Lattice-based homomorphic encryption exists in theory, but practical -instantiations have ciphertext sizes, proving costs, and threshold -decryption complexities that are orders of magnitude larger than -El Gamal on an elliptic curve. The homomorphic tally -(component-wise point addition of Pallas points) and the threshold -decryption (Shamir/Feldman secret sharing with Lagrange interpolation -over a scalar field) are both simple precisely because El Gamal -operates in the same algebraic setting as the rest of the protocol. -Replacing it would require a fundamentally different threshold -protocol and circuit design for the Vote Proof and Vote Reveal Proof. - -The practical consequence is that vote-amount privacy has a finite -horizon tied to quantum computing timelines. Ciphertexts are stored -on-chain permanently; an adversary who records them today could decrypt -individual share amounts once a cryptographically relevant quantum -computer exists. Voter *identity* is unaffected (alternate nullifier -unlinkability relies on Poseidon preimage resistance, not on El Gamal), -but *how much* a voter allocated to each option would be exposed. - -This tradeoff is accepted for initial deployment. Per-round key rotation -(each round uses a fresh $\mathsf{ea}\_\mathsf{sk}$) limits a classical -compromise to a single round, and vote splitting across $N_s$ shares -means a quantum adversary would recover individual shares rather than -complete ballot allocations unless it also breaks the vote commitment -unlinkability (which depends on the Poseidon-based blinded share -commitments, not on El Gamal). Post-quantum migration is tracked as an -open issue. +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 @@ -1535,17 +1423,9 @@ finalization of this ZIP. # Open issues -- Per-validator Chaum–Pedersen DLEQ proofs for partial decryptions - would allow immediate identification of misbehaving validators at - tally time, converting the current liveness cost (subset search) to - $O(1)$ verification. See [Why No Per-Validator DLEQ Proofs]. -- Feldman VSS commitments from the dealer during the key ceremony - would let each validator verify that its Shamir share is consistent - with $\mathsf{ea}\_\mathsf{pk}$, removing the trust assumption on the - dealer. See [Why Threshold Secret Sharing]. -- Distributed key generation (DKG) would eliminate the trusted dealer - entirely, producing $\mathsf{ea}\_\mathsf{pk}$ without any single party - ever holding $\mathsf{ea}\_\mathsf{sk}$. See [Why Threshold Secret Sharing]. +- Open issues related to the EA key ceremony (per-validator DLEQ proofs, + Feldman VSS commitments, distributed key generation, and post-quantum + encryption) are tracked in [^ea-ceremony]. - Hardware wallet firmware with voting-aware signing (e.g., a governance network byte) would allow a simplified Delegation Proof circuit that removes the dummy signed note scaffolding (signed note @@ -1567,17 +1447,6 @@ finalization of this ZIP. to track VCT paths (e.g., full server-side path retrieval), this tradeoff should be revisited. See [Why a Send-Based VAN Model]. -- Post-quantum aggregatable encryption would eliminate the long-term - "harvest now, decrypt later" risk to vote-amount privacy. On-chain - El Gamal ciphertexts are permanent; a future quantum adversary could - decrypt individual share amounts for any recorded round. No production- - ready post-quantum scheme currently offers both additive homomorphism - and efficient threshold decryption. If such a scheme matures, the - El Gamal layer (encryption in the Vote Proof, ciphertext verification - in the Vote Reveal Proof, and the homomorphic tally procedure) could - be replaced without changing the commitment, nullifier, or Merkle - membership components of the protocol. - See [Why Classical El Gamal Rather Than Post-Quantum Encryption]. - The VCT depth of 24 ($2^{24} \approx 16.7$ million leaves) is sufficient for current governance usage projections, but may need to be increased via a vote chain upgrade if participation grows @@ -1594,8 +1463,6 @@ finalization of this ZIP. [^protocol]: [Zcash Protocol Specification, Version 2025.6.3 [NU6.1] or later](protocol/protocol.pdf) -[^protocol-pallasandvesta]: [Zcash Protocol Specification, Version 2025.6.3 [NU6.1]. Section 5.4.9.6: Pallas and Vesta](protocol/protocol.pdf#pallasandvesta) - [^protocol-concretespendauthsig]: [Zcash Protocol Specification, Version 2025.6.3 [NU6.1]. Section 5.4.7.1: Spend Authorization Signature (Orchard)](protocol/protocol.pdf#concretespendauthsig) [^poseidon]: [Poseidon: A New Hash Function for Zero-Knowledge Proof Systems](https://eprint.iacr.org/2019/458) @@ -1606,22 +1473,8 @@ finalization of this ZIP. [^ea-ceremony]: [Election Authority Key Ceremony](draft-valargroup-ea-key-ceremony) -[^chaum-pedersen]: [Chaum, D. and Pedersen, T.P. "Wallet Databases with Observers." CRYPTO 1992](https://link.springer.com/chapter/10.1007/3-540-48071-4_7) - -[^elgamal]: [T. ElGamal, "A public key cryptosystem and a signature scheme based on discrete logarithms", IEEE Transactions on Information Theory, vol. 31, no. 4, pp. 469-472, 1985](https://doi.org/10.1109/TIT.1985.1057074) - -[^shamir]: [A. Shamir, "How to share a secret", Communications of the ACM, vol. 22, no. 11, pp. 612-613, 1979](https://doi.org/10.1145/359168.359176) - -[^feldman]: [P. Feldman, "A practical scheme for non-interactive verifiable secret sharing", in Proceedings of the 28th IEEE Symposium on Foundations of Computer Science, pp. 427-437, 1987](https://doi.org/10.1109/SFCS.1987.4) - -[^ecies]: [V. Shoup, "A Proposal for an ISO Standard for Public Key Encryption", version 2.1, 2001](https://www.shoup.net/papers/iso-2_1.pdf) - -[^bsgs]: [D. Shanks, "Class number, a theory of factorization, and genera", in Proceedings of Symposia in Pure Mathematics, vol. 20, pp. 415-440, 1971](https://doi.org/10.1090/pspum/020/0316385) - [^halo2]: [S. Bowe, J. Grigg, and D. Hopwood, "Recursive Proof Composition without a Trusted Setup", 2019](https://eprint.iacr.org/2019/1021) -[^cometbft]: [CometBFT Specification](https://docs.cometbft.com/v1/spec/) - [^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) From 24ae03410bd9e3765845dcf17cc5d28557be59b9 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 16 Mar 2026 14:53:04 -0300 Subject: [PATCH 31/56] updates Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 154 +++++++++++++++++++++-- 1 file changed, 142 insertions(+), 12 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 831d48ff0..30bb872cb 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -289,6 +289,101 @@ 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)$. +## Chaum-Pedersen DLEQ Proof + +The protocol uses a non-interactive Chaum-Pedersen proof of +discrete-log equality (DLEQ) [^chaum-pedersen] to verify correct usage +of secret key material during decryption. The proof is instantiated +over the Pallas curve with a Fiat-Shamir challenge derived from +BLAKE2b-256 [^blake2]. + +**Statement.** Given two pairs of Pallas points $(G, P)$ and $(H, Q)$, +a DLEQ proof demonstrates: + +$$\log_G(P) = \log_H(Q)$$ + +That is, the same scalar $x$ satisfies $P = [x]\, G$ and +$Q = [x]\, H$. + +A proof is a pair of Pallas scalars $(e, z)$, serialized as 64 bytes +($e \mathbin\| z$, 32 bytes each). + +### Proof Generation + +The prover, holding secret $x$, computes: + +1. Sample $k \leftarrow \mathbb{F}_{q_{\mathbb{P}}}$ uniformly at random. +2. $R_1 = [k]\, G, \quad R_2 = [k]\, H$. +3. $e = \mathsf{DLEQChallenge}(G, P, H, Q, R_1, R_2)$. +4. $z = k + e \cdot x$. +5. Output $(e, z)$. + +### Proof Verification + +The verifier, given $(G, P, H, Q)$ and a proof $(e, z)$, checks: + +1. Parse $e$ and $z$ as canonical Pallas scalar field elements. Reject + if either is a non-canonical encoding. +2. $R_1 = [z]\, G - [e]\, P$. +3. $R_2 = [z]\, H - [e]\, Q$. +4. $e' = \mathsf{DLEQChallenge}(G, P, H, Q, R_1, R_2)$. +5. Accept if and only if $e' = e$. + +### Challenge Derivation + +The Fiat-Shamir challenge binds the proof to the full statement and +the prover's commitments: + +$$e = \mathsf{HashToScalar}\bigl(\text{"svote-dleq-v1"} \mathbin\| \mathsf{compress}(G) \mathbin\| \mathsf{compress}(P) \mathbin\| \mathsf{compress}(H) \mathbin\| \mathsf{compress}(Q) \mathbin\| \mathsf{compress}(R_1) \mathbin\| \mathsf{compress}(R_2)\bigr)$$ + +where $\mathsf{compress}$ denotes the 32-byte affine compressed point +encoding and $\mathsf{HashToScalar}$ applies BLAKE2b-256 (unkeyed) to +the concatenation and maps the 32-byte digest to a Pallas scalar. The +domain tag `"svote-dleq-v1"` (13 bytes, ASCII) prevents cross-protocol +challenge reuse. + +All input points MUST be validated as on-curve Pallas points at +deserialization time. The proof contains only scalars; each MUST be a +canonical encoding in $\mathbb{F}_{q_{\mathbb{P}}}$. + +### Partial Decryption Proof + +In threshold mode, each validator $i$ proves that their partial +decryption was computed using their correct Shamir share $s_i$. The +DLEQ proof is instantiated with: + +- $P = \mathsf{VK}\_i = [s_i]\, G$ — the validator's public + verification key, derived from their Shamir share. +- $H = C_1^{\mathsf{agg}}$ — the first component of the aggregate + ciphertext. +- $Q = D_i = [s_i]\, C_1^{\mathsf{agg}}$ — the validator's partial + decryption. +- $x = s_i$ — the validator's Shamir share (private). + +The proof demonstrates +$\log_G(\mathsf{VK}\_i) = \log_{C_1^{\mathsf{agg}}}(D_i)$, confirming +that the same share used to derive $\mathsf{VK}\_i$ was used to compute +$D_i$. + +### Aggregate Decryption Proof + +When the full election authority secret key is available (e.g., for +testing), correct decryption of the +aggregate ciphertext can be proven directly. The DLEQ proof is +instantiated with: + +- $P = \mathsf{ea}\_\mathsf{pk} = [\mathsf{ea}\_\mathsf{sk}]\, G$. +- $H = C_1^{\mathsf{agg}}$. +- $Q = C_2^{\mathsf{agg}} - [v]\, G$ — the decryption point, where $v$ + is the claimed plaintext total. +- $x = \mathsf{ea}\_\mathsf{sk}$. + +The proof demonstrates +$\log_G(\mathsf{ea}\_\mathsf{pk}) = \log_{C_1^{\mathsf{agg}}}(C_2^{\mathsf{agg}} - [v]\, G)$, +confirming that the decryptor knows $\mathsf{ea}\_\mathsf{sk}$ and +correctly derived $v$. + + ## Data Structures ### Vote Authority Note (VAN) @@ -1110,10 +1205,31 @@ independently verify the aggregation. **Step 2: Threshold decryption and public verification.** At least $t$ validators produce partial decryptions that are combined via Lagrange -interpolation to recover $\mathsf{total}\_\mathsf{ballots}$. The procedure, -including partial decryption submission, Lagrange combination, and -public verification, is specified in [^ea-ceremony]. Individual vote -amounts are never revealed; only the aggregate total per +interpolation to recover $\mathsf{total}\_\mathsf{ballots}$. The procedure +for partial decryption submission and Lagrange combination is specified +in [^ea-ceremony]. + +Each validator $i$ computes $D_i = [s_i]\, C_1^{\mathsf{agg}}$ using +their Shamir share $s_i$ and submits $D_i$ along with a +Chaum-Pedersen DLEQ proof demonstrating correct share usage (see +[Partial Decryption Proof]). A verifier MUST check each per-validator +proof against the validator's published verification key +$\mathsf{VK}\_i$ and $C_1^{\mathsf{agg}}$ before accepting $D_i$ for +Lagrange combination. + +The combined result MUST be publicly verified. Given the aggregate +ciphertext $\mathsf{agg} = (C_1^{\mathsf{agg}}, C_2^{\mathsf{agg}})$ and +the Lagrange-combined partial decryption point $D_{\mathsf{combined}}$, +any party can check: + +$$C_2^{\mathsf{agg}} - D_{\mathsf{combined}} = [\mathsf{total}\_\mathsf{ballots}]\, G$$ + +When the full election authority secret key is available (e.g., for +testing or single-EA configurations), correct decryption MAY +alternatively be proven via the aggregate variant of the DLEQ proof +(see [Aggregate Decryption Proof]). + +Individual vote amounts are never revealed; only the aggregate total per (proposal, decision) pair. @@ -1307,12 +1423,22 @@ authority check is intended. An additional non-zero gate provides defense-in-depth by rejecting $\mathsf{proposal}\_\mathsf{id} = 0$ on active rows. -## Why Threshold Secret Sharing and No Per-Validator DLEQ Proofs +## Why Threshold Secret Sharing -See [^ea-ceremony] for the rationale behind threshold secret sharing -(including the trusted dealer model and the omission of Feldman VSS -commitments) and the omission of per-validator DLEQ proofs for partial -decryptions. +See [^ea-ceremony] for the rationale behind threshold secret sharing, +including the trusted dealer model and the omission of Feldman VSS +commitments. + +## Why Per-Validator DLEQ Proofs + +The Chaum-Pedersen DLEQ proof defined in [Chaum-Pedersen DLEQ Proof] +is instantiated per-validator in [Partial Decryption Proof]: each +validator proves $\log_G(\mathsf{VK}\_i) = \log_{C_1}(D_i)$, +demonstrating that their partial decryption $D_i$ was computed using +the same Shamir share $s_i$ that defines their public verification key +$\mathsf{VK}\_i$. This enables on-chain rejection of incorrect partial +decryptions before Lagrange combination, without requiring the verifier +to know $s_i$ or reconstruct $\mathsf{ea}\_\mathsf{sk}$. ## Why a Send-Based VAN Model @@ -1423,9 +1549,9 @@ finalization of this ZIP. # Open issues -- Open issues related to the EA key ceremony (per-validator DLEQ proofs, - Feldman VSS commitments, distributed key generation, and post-quantum - encryption) are tracked in [^ea-ceremony]. +- Open issues related to the EA key ceremony (Feldman VSS commitments, + distributed key generation, and post-quantum encryption) are tracked + in [^ea-ceremony]. - Hardware wallet firmware with voting-aware signing (e.g., a governance network byte) would allow a simplified Delegation Proof circuit that removes the dummy signed note scaffolding (signed note @@ -1473,6 +1599,10 @@ finalization of this ZIP. [^ea-ceremony]: [Election Authority Key Ceremony](draft-valargroup-ea-key-ceremony) +[^chaum-pedersen]: [D. Chaum and T. P. Pedersen, "Wallet Databases with Observers", CRYPTO 1992](https://link.springer.com/chapter/10.1007/3-540-48071-4_7) + +[^blake2]: [J.-P. Aumasson, S. Neves, Z. Wilcox-O'Hearn, and C. Winnerlein, "BLAKE2: simpler, smaller, fast as MD5", 2013](https://blake2.net/blake2.pdf) + [^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) From 1b480476ec993f97457bd7e369f2746ac2165598 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 16 Mar 2026 15:00:31 -0300 Subject: [PATCH 32/56] updates Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 30bb872cb..424caa90a 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -1225,7 +1225,7 @@ any party can check: $$C_2^{\mathsf{agg}} - D_{\mathsf{combined}} = [\mathsf{total}\_\mathsf{ballots}]\, G$$ When the full election authority secret key is available (e.g., for -testing or single-EA configurations), correct decryption MAY +testing), correct decryption MAY alternatively be proven via the aggregate variant of the DLEQ proof (see [Aggregate Decryption Proof]). From 351bd17f553395a1dfbb28abd0f805b86197642b Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 16 Mar 2026 16:05:27 -0300 Subject: [PATCH 33/56] updates Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 38 +++++++++++++++++------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 424caa90a..24b50347c 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -368,20 +368,38 @@ $D_i$. ### Aggregate Decryption Proof When the full election authority secret key is available (e.g., for -testing), correct decryption of the -aggregate ciphertext can be proven directly. The DLEQ proof is +testing), correct decryption of the aggregate ciphertext can be proven +directly via two complementary DLEQ variants. + +**Partial-decrypt DLEQ (key binding).** The full secret key is treated +as a single "share" in the partial-decrypt protocol. The proof is instantiated with: -- $P = \mathsf{ea}\_\mathsf{pk} = [\mathsf{ea}\_\mathsf{sk}]\, G$. +- $\mathsf{VK} = \mathsf{ea\_pk} = [\mathsf{ea\_sk}]\, G$. - $H = C_1^{\mathsf{agg}}$. -- $Q = C_2^{\mathsf{agg}} - [v]\, G$ — the decryption point, where $v$ - is the claimed plaintext total. -- $x = \mathsf{ea}\_\mathsf{sk}$. +- $D = [\mathsf{ea\_sk}]\, C_1^{\mathsf{agg}}$. +- $x = \mathsf{ea\_sk}$. -The proof demonstrates -$\log_G(\mathsf{ea}\_\mathsf{pk}) = \log_{C_1^{\mathsf{agg}}}(C_2^{\mathsf{agg}} - [v]\, G)$, -confirming that the decryptor knows $\mathsf{ea}\_\mathsf{sk}$ and -correctly derived $v$. +This demonstrates +$\log_G(\mathsf{ea\_pk}) = \log_{C_1^{\mathsf{agg}}}(D)$, +confirming the decryptor used the correct secret key. The plaintext +total is then verified separately: +$C_2^{\mathsf{agg}} - D = [v]\, G$. + +**Aggregate DLEQ (plaintext binding).** The proof binds the claimed +plaintext $v$ directly into the Fiat-Shamir challenge. It is +instantiated with: + +- $P = \mathsf{ea\_pk} = [\mathsf{ea\_sk}]\, G$. +- $H = C_1^{\mathsf{agg}}$. +- $Q = C_2^{\mathsf{agg}} - [v]\, G$. +- $x = \mathsf{ea\_sk}$. + +The verifier recomputes $Q$ from $C_2^{\mathsf{agg}}$ and $v$, so +the proof demonstrates +$\log_G(\mathsf{ea\_pk}) = \log_{C_1^{\mathsf{agg}}}(C_2^{\mathsf{agg}} - [v]\, G)$, +confirming that the decryptor knows $\mathsf{ea\_sk}$ and correctly +derived $v$ in a single verification step. ## Data Structures From 494d78f12bf27bee6add106e6404be9cb19b1981 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 16 Mar 2026 21:27:33 -0300 Subject: [PATCH 34/56] link to ea ceremony for dleq Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 125 ++--------------------- 1 file changed, 9 insertions(+), 116 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 24b50347c..e1c81317c 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -293,114 +293,20 @@ $\mathsf{Enc}(v, r) = \bigl([r]\, G,\; [v]\, G + [r]\, \mathsf{ea}\_\mathsf{pk}\ The protocol uses a non-interactive Chaum-Pedersen proof of discrete-log equality (DLEQ) [^chaum-pedersen] to verify correct usage -of secret key material during decryption. The proof is instantiated -over the Pallas curve with a Fiat-Shamir challenge derived from -BLAKE2b-256 [^blake2]. - -**Statement.** Given two pairs of Pallas points $(G, P)$ and $(H, Q)$, -a DLEQ proof demonstrates: - -$$\log_G(P) = \log_H(Q)$$ - -That is, the same scalar $x$ satisfies $P = [x]\, G$ and -$Q = [x]\, H$. - -A proof is a pair of Pallas scalars $(e, z)$, serialized as 64 bytes -($e \mathbin\| z$, 32 bytes each). - -### Proof Generation - -The prover, holding secret $x$, computes: - -1. Sample $k \leftarrow \mathbb{F}_{q_{\mathbb{P}}}$ uniformly at random. -2. $R_1 = [k]\, G, \quad R_2 = [k]\, H$. -3. $e = \mathsf{DLEQChallenge}(G, P, H, Q, R_1, R_2)$. -4. $z = k + e \cdot x$. -5. Output $(e, z)$. - -### Proof Verification - -The verifier, given $(G, P, H, Q)$ and a proof $(e, z)$, checks: - -1. Parse $e$ and $z$ as canonical Pallas scalar field elements. Reject - if either is a non-canonical encoding. -2. $R_1 = [z]\, G - [e]\, P$. -3. $R_2 = [z]\, H - [e]\, Q$. -4. $e' = \mathsf{DLEQChallenge}(G, P, H, Q, R_1, R_2)$. -5. Accept if and only if $e' = e$. - -### Challenge Derivation - -The Fiat-Shamir challenge binds the proof to the full statement and -the prover's commitments: - -$$e = \mathsf{HashToScalar}\bigl(\text{"svote-dleq-v1"} \mathbin\| \mathsf{compress}(G) \mathbin\| \mathsf{compress}(P) \mathbin\| \mathsf{compress}(H) \mathbin\| \mathsf{compress}(Q) \mathbin\| \mathsf{compress}(R_1) \mathbin\| \mathsf{compress}(R_2)\bigr)$$ - -where $\mathsf{compress}$ denotes the 32-byte affine compressed point -encoding and $\mathsf{HashToScalar}$ applies BLAKE2b-256 (unkeyed) to -the concatenation and maps the 32-byte digest to a Pallas scalar. The -domain tag `"svote-dleq-v1"` (13 bytes, ASCII) prevents cross-protocol -challenge reuse. - -All input points MUST be validated as on-curve Pallas points at -deserialization time. The proof contains only scalars; each MUST be a -canonical encoding in $\mathbb{F}_{q_{\mathbb{P}}}$. +of secret key material during decryption. The DLEQ proof construction, including the statement, proof generation, proof verification, and +Fiat-Shamir challenge derivation with the `"svote-dleq-v1"` domain +tag, is defined in [^ea-ceremony]. A proof is a pair of Pallas scalars +$(e, z)$ demonstrating $\log_G(P) = \log_H(Q)$. ### Partial Decryption Proof In threshold mode, each validator $i$ proves that their partial decryption was computed using their correct Shamir share $s_i$. The -DLEQ proof is instantiated with: - -- $P = \mathsf{VK}\_i = [s_i]\, G$ — the validator's public - verification key, derived from their Shamir share. -- $H = C_1^{\mathsf{agg}}$ — the first component of the aggregate - ciphertext. -- $Q = D_i = [s_i]\, C_1^{\mathsf{agg}}$ — the validator's partial - decryption. -- $x = s_i$ — the validator's Shamir share (private). - -The proof demonstrates -$\log_G(\mathsf{VK}\_i) = \log_{C_1^{\mathsf{agg}}}(D_i)$, confirming -that the same share used to derive $\mathsf{VK}\_i$ was used to compute -$D_i$. - -### Aggregate Decryption Proof - -When the full election authority secret key is available (e.g., for -testing), correct decryption of the aggregate ciphertext can be proven -directly via two complementary DLEQ variants. - -**Partial-decrypt DLEQ (key binding).** The full secret key is treated -as a single "share" in the partial-decrypt protocol. The proof is -instantiated with: - -- $\mathsf{VK} = \mathsf{ea\_pk} = [\mathsf{ea\_sk}]\, G$. -- $H = C_1^{\mathsf{agg}}$. -- $D = [\mathsf{ea\_sk}]\, C_1^{\mathsf{agg}}$. -- $x = \mathsf{ea\_sk}$. - -This demonstrates -$\log_G(\mathsf{ea\_pk}) = \log_{C_1^{\mathsf{agg}}}(D)$, -confirming the decryptor used the correct secret key. The plaintext -total is then verified separately: -$C_2^{\mathsf{agg}} - D = [v]\, G$. - -**Aggregate DLEQ (plaintext binding).** The proof binds the claimed -plaintext $v$ directly into the Fiat-Shamir challenge. It is -instantiated with: - -- $P = \mathsf{ea\_pk} = [\mathsf{ea\_sk}]\, G$. -- $H = C_1^{\mathsf{agg}}$. -- $Q = C_2^{\mathsf{agg}} - [v]\, G$. -- $x = \mathsf{ea\_sk}$. - -The verifier recomputes $Q$ from $C_2^{\mathsf{agg}}$ and $v$, so -the proof demonstrates -$\log_G(\mathsf{ea\_pk}) = \log_{C_1^{\mathsf{agg}}}(C_2^{\mathsf{agg}} - [v]\, G)$, -confirming that the decryptor knows $\mathsf{ea\_sk}$ and correctly -derived $v$ in a single verification step. - +DLEQ proof instantiation for partial decryption — with +$P = \mathsf{VK}\_i = [s_i]\, G$, +$H = C_1^{\mathsf{agg}}$, +$Q = D_i = [s_i]\, C_1^{\mathsf{agg}}$, and +$x = s_i$ — is defined in [^ea-ceremony]. ## Data Structures @@ -1447,17 +1353,6 @@ See [^ea-ceremony] for the rationale behind threshold secret sharing, including the trusted dealer model and the omission of Feldman VSS commitments. -## Why Per-Validator DLEQ Proofs - -The Chaum-Pedersen DLEQ proof defined in [Chaum-Pedersen DLEQ Proof] -is instantiated per-validator in [Partial Decryption Proof]: each -validator proves $\log_G(\mathsf{VK}\_i) = \log_{C_1}(D_i)$, -demonstrating that their partial decryption $D_i$ was computed using -the same Shamir share $s_i$ that defines their public verification key -$\mathsf{VK}\_i$. This enables on-chain rejection of incorrect partial -decryptions before Lagrange combination, without requiring the verifier -to know $s_i$ or reconstruct $\mathsf{ea}\_\mathsf{sk}$. - ## Why a Send-Based VAN Model The Vote Proof consumes the old VAN and produces a new one with the @@ -1619,8 +1514,6 @@ finalization of this ZIP. [^chaum-pedersen]: [D. Chaum and T. P. Pedersen, "Wallet Databases with Observers", CRYPTO 1992](https://link.springer.com/chapter/10.1007/3-540-48071-4_7) -[^blake2]: [J.-P. Aumasson, S. Neves, Z. Wilcox-O'Hearn, and C. Winnerlein, "BLAKE2: simpler, smaller, fast as MD5", 2013](https://blake2.net/blake2.pdf) - [^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) From 27378a3a1d627b631a96e2448bf4924cb6d8ce46 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 16 Mar 2026 21:30:29 -0300 Subject: [PATCH 35/56] link to ea ceremony Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index e1c81317c..95769bf1f 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -1349,9 +1349,7 @@ on active rows. ## Why Threshold Secret Sharing -See [^ea-ceremony] for the rationale behind threshold secret sharing, -including the trusted dealer model and the omission of Feldman VSS -commitments. +See [^ea-ceremony] for the rationale behind threshold secret sharing. ## Why a Send-Based VAN Model @@ -1462,9 +1460,7 @@ finalization of this ZIP. # Open issues -- Open issues related to the EA key ceremony (Feldman VSS commitments, - distributed key generation, and post-quantum encryption) are tracked - in [^ea-ceremony]. +- Open issues related to the EA key ceremony are tracked in [^ea-ceremony]. - Hardware wallet firmware with voting-aware signing (e.g., a governance network byte) would allow a simplified Delegation Proof circuit that removes the dummy signed note scaffolding (signed note From fde35ef0d0e938ed324ad8741ced952fcbc19771 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 17 Mar 2026 21:18:18 -0300 Subject: [PATCH 36/56] edits Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 26 ++++++++---------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 95769bf1f..e472838a9 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -197,21 +197,16 @@ see [^pir-governance]. - A holder's on-chain identity (spending key, standard nullifiers) is not linkable to their voting activity. -- Double-delegation of the same Orchard note within a voting round is - detectable via deterministic governance nullifiers. -- Double-voting with the same Vote Authority Note is detectable via - deterministic VAN nullifiers. -- Double-counting of the same vote share is detectable via deterministic - share nullifiers. +- 1 user-facing signature per 5 notes. +- 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 protocol supports up to 15 proposals per voting round. -- The Vote Commitment Tree has capacity for at least several million +- The Vote Commitment Tree has capacity for 2^24 = 16.7 million leaves (combined VANs and VCs) per voting round. -- The protocol supports delegation of voting authority to a third-party - hotkey. - 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. @@ -219,18 +214,13 @@ see [^pir-governance]. # Non-requirements -- The consensus mechanism and operational parameters of the vote chain - are out of scope for this ZIP. +- 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 expected to - be specified in a separate ZIP. -- Distributed key generation of the election authority's El Gamal keypair - among validators (producing $\mathsf{ea}\_\mathsf{pk}$ without any single - party constructing $\mathsf{ea}\_\mathsf{sk}$) is out of scope; the current - design uses a trusted dealer. + 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 @@ -1508,6 +1498,8 @@ finalization of this ZIP. [^ea-ceremony]: [Election Authority Key Ceremony](draft-valargroup-ea-key-ceremony) +[^voting-setup]: [Zcash Shielded Coinholder Voting](draft-valargroup-shielded-voting-setup) + [^chaum-pedersen]: [D. Chaum and T. P. Pedersen, "Wallet Databases with Observers", CRYPTO 1992](https://link.springer.com/chapter/10.1007/3-540-48071-4_7) [^halo2]: [S. Bowe, J. Grigg, and D. Hopwood, "Recursive Proof Composition without a Trusted Setup", 2019](https://eprint.iacr.org/2019/1021) From db83c8388cccd9f02932ae8d2bccf1a80758d582 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 17 Mar 2026 22:04:12 -0300 Subject: [PATCH 37/56] edits Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 106 ++++++++++++++--------- 1 file changed, 64 insertions(+), 42 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index e472838a9..5ed795a98 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -197,7 +197,7 @@ see [^pir-governance]. - A holder's on-chain identity (spending key, standard nullifiers) is not linkable to their voting activity. -- 1 user-facing signature per 5 notes. +- 1 user-facing signatures per 5 notes. - 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 @@ -279,25 +279,6 @@ 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)$. -## Chaum-Pedersen DLEQ Proof - -The protocol uses a non-interactive Chaum-Pedersen proof of -discrete-log equality (DLEQ) [^chaum-pedersen] to verify correct usage -of secret key material during decryption. The DLEQ proof construction, including the statement, proof generation, proof verification, and -Fiat-Shamir challenge derivation with the `"svote-dleq-v1"` domain -tag, is defined in [^ea-ceremony]. A proof is a pair of Pallas scalars -$(e, z)$ demonstrating $\log_G(P) = \log_H(Q)$. - -### Partial Decryption Proof - -In threshold mode, each validator $i$ proves that their partial -decryption was computed using their correct Shamir share $s_i$. The -DLEQ proof instantiation for partial decryption — with -$P = \mathsf{VK}\_i = [s_i]\, G$, -$H = C_1^{\mathsf{agg}}$, -$Q = D_i = [s_i]\, C_1^{\mathsf{agg}}$, and -$x = s_i$ — is defined in [^ea-ceremony]. - ## Data Structures ### Vote Authority Note (VAN) @@ -343,12 +324,14 @@ 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. -**Lifecycle.** A VAN is created during delegation (Phase 1) and consumed -during voting (Phase 2), which produces 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. +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) @@ -367,13 +350,18 @@ where: - $\mathsf{vote}\_\mathsf{decision} \in \{ 0 .. q_{\mathbb{P}}-1 \}$ — the voter's choice (0-indexed into the proposal's declared options). -A VC is created during voting (Phase 2) and opened during share reveal -(Phase 4/5). The VC hash is posted on-chain as a public input of the -Vote Proof and inserted into the VCT, but its preimage fields +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}$) -are private witnesses in that proof. During share reveal, the Vote -Reveal Proof proves membership in the VCT without exposing which VC is -being opened. +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 @@ -416,13 +404,17 @@ 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 VCT is an incremental Merkle tree [^protocol-merkletree] of depth -$\mathsf{MerkleDepth}^{\mathsf{vct}} = 24$ that stores both VANs and VCs as leaves. -It uses the same append-only data structure as the Orchard note -commitment tree, but with Poseidon [^poseidon] over the Pallas -scalar field for internal node hashing instead of Sinsemilla, +The VCT MUST be an incremental Merkle tree [^protocol-merkletree] of +depth $\mathsf{MerkleDepth}^{\mathsf{vct}} = 24$ that stores both VANs +and VCs as leaves. It MUST use the same append-only data structure as +the Orchard note commitment tree, but with Poseidon [^poseidon] over +the Pallas scalar field for internal node hashing instead of Sinsemilla, using the same instantiation as the nullifier non-membership tree [^balance-proof]. @@ -431,21 +423,19 @@ 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 are inserted in transaction order: a delegation transaction +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 maintains three disjoint 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 is append-only within a voting round. The vote chain rejects -any transaction that publishes a nullifier already present in the -corresponding set. +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. ### Domain Separator Tags @@ -1178,6 +1168,25 @@ never observes plaintext vote amounts or voter identities. The vote chain's consensus mechanism, block structure, transaction encoding, and API are out of scope for this ZIP. +## Chaum-Pedersen DLEQ Proof + +The protocol uses a non-interactive Chaum-Pedersen proof of +discrete-log equality (DLEQ) [^chaum-pedersen] to verify correct usage +of secret key material during decryption. The DLEQ proof construction, including the statement, proof generation, proof verification, and +Fiat-Shamir challenge derivation with the `"svote-dleq-v1"` domain +tag, is defined in [^ea-ceremony]. A proof is a pair of Pallas scalars +$(e, z)$ demonstrating $\log_G(P) = \log_H(Q)$. + +### Partial Decryption Proof + +In threshold mode, each validator $i$ proves that their partial +decryption was computed using their correct Shamir share $s_i$. The +DLEQ proof instantiation for partial decryption — with +$P = \mathsf{VK}\_i = [s_i]\, G$, +$H = C_1^{\mathsf{agg}}$, +$Q = D_i = [s_i]\, C_1^{\mathsf{agg}}$, and +$x = s_i$ — is defined in [^ea-ceremony]. + # Rationale @@ -1235,6 +1244,19 @@ entirely. Delegation preserves the hardware wallet's role as the sole custodian of spending keys while enabling full participation in the voting protocol. +## 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 From 4e17552262821b91e5916027566f61aedc6db6bd Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 17 Mar 2026 22:15:33 -0300 Subject: [PATCH 38/56] deterministic hotkey spec Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 45 +++++++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 5ed795a98..934e491a6 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -488,11 +488,24 @@ 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 MAY be generated deterministically from a seed (e.g., via -Blake2b with a voting-specific personalization) or sampled randomly. -The generation method is an application concern; the protocol requires -only that the resulting key material satisfies the standard Orchard -key relationships above. +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]. ## Ballot Scaling @@ -1244,6 +1257,24 @@ 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. Additionally, the El Gamal +encryption randomness and per-share blind factors used in the Vote +Proof are derived deterministically from the spending key via a +domain-separated PRF, so crash recovery extends to per-vote secrets +as well. + +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}$) independently, +and any storage failure would be unrecoverable. + ## Why 5 Notes per Delegation The Delegation Proof fixes the note slot count at 5 (with padding for @@ -1512,6 +1543,10 @@ finalization of this ZIP. [^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) From f2a4c5802bfa863e9c170687aa0e16065c33fff4 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 17 Mar 2026 22:22:50 -0300 Subject: [PATCH 39/56] Ballot Scaling edits Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 49 +++++++++++------------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 934e491a6..8618bde12 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -510,42 +510,26 @@ See [Why Deterministic Hotkey Derivation]. ## Ballot Scaling -Orchard note values are denominated in zatoshi. To reduce the bit-width -of values flowing through El Gamal encryption and downstream range -checks, the protocol converts zatoshi to ballots: +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. -This conversion is enforced in the Delegation Proof circuit. The prover -witnesses $\mathsf{num}\_\mathsf{ballots}$ and a remainder $r$, and the circuit -constrains: +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}$ -3. $\mathsf{num}\_\mathsf{ballots} \geq 1$ and $\mathsf{num}\_\mathsf{ballots} \leq 2^{30}$ +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 on $\mathsf{num}\_\mathsf{ballots}$ accommodates up to -$\approx 134$ million ZEC, well above the 21 million ZEC supply cap. -The minimum of 1 ballot prevents dust delegations (holdings below -0.125 ZEC) from producing voting authority. - -
- - -### Rationale for remainder range - - -The remainder 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. -
+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 @@ -1234,6 +1218,17 @@ bounded discrete-log search at tally time 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 From 3569510e1ef828e2f92a1211ca7c20c7f6352cf3 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 17 Mar 2026 22:37:37 -0300 Subject: [PATCH 40/56] spec conformance for delegation phase Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 56 +++++++++++++----------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 8618bde12..8df93a511 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -540,11 +540,12 @@ 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 per-note ownership checks (note commitment integrity, Merkle path -validity, nullifier derivation, diversified address integrity, and -nullifier non-membership) are identical to those in the Batched Claim -circuit defined in [^balance-proof]. This section specifies only the -conditions that extend beyond the Balance Proof. +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 @@ -584,19 +585,20 @@ in [^balance-proof]: #### Conditions **Per-note conditions (5 note slots, with padding).** For each note -$i \in \{1 \ldots 5\}$, the following conditions from the Batched Claim -circuit [^balance-proof] apply: +$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}$ owns all 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 have value 0. +- Padded notes MUST have value 0. -**Governance nullifier derivation.** For each real note $i$: +**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)$$ @@ -607,31 +609,34 @@ 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 is a dummy note with value 0. -Its note commitment $\mathsf{cm}^{\mathsf{signed}}$ is correctly constructed, and +**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 signed note's $\text{ρ}^{\mathsf{signed}}$ is -deterministically bound to the delegation context: +**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.** $\mathsf{rk} = \mathsf{SpendAuthSig}^{\mathsf{Orchard}}\mathsf{.RandomizePublic}(\alpha, \mathsf{ak}^{\mathbb{P}})$. +**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 signed note's -address belongs to $(\mathsf{ak}, \mathsf{nk})$. +**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.** $\mathsf{cmx}\_\mathsf{new}$ is a correctly -constructed note commitment to an output note addressed to the -governance hotkey. +**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. -**VAN integrity.** 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)]): +**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)$$ @@ -639,7 +644,8 @@ $$\mathsf{van} = \mathsf{Poseidon}\bigl(\mathsf{van}\_\mathsf{core}, \mathsf{gov where $\mathsf{MAX}\_{\mathsf{PROPOSAL}\_\mathsf{AUTHORITY}} = 2^{16} - 1$. -**Ballot scaling.** $\mathsf{num}\_\mathsf{ballots} = \lfloor \sum v_i / 12{,}500{,}000 \rfloor$, +**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 From 4066f3e6c26b89aba695708fdc16f5976402e71d Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 17 Mar 2026 22:49:42 -0300 Subject: [PATCH 41/56] vote proof phase edits Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 120 ++++++++++++----------- 1 file changed, 63 insertions(+), 57 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 8df93a511..bfb58bc53 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -689,7 +689,8 @@ A delegation transaction submitted to the vote chain MUST contain: 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. +El Gamal-encrypted shares to the chosen proposal and decision. The +Vote Proof circuit MUST enforce all conditions specified below. #### Public Inputs @@ -743,89 +744,77 @@ The prover knows: ##### VAN Ownership and Spending -**Condition 1: Merkle tree membership.** The old VAN exists in the VCT: -$(\mathsf{path}^{\mathsf{vct}}, \mathsf{pos}^{\mathsf{vct}})$ is 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 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 old VAN commitment matches the -claimed fields (using the two-layer construction defined in -[Vote Authority Note (VAN)]): +**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)]): $$\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 VAN's address -belongs to the voting key: +**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 randomized voting public key is -a valid rerandomization: +**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 public $\mathsf{van}\_\mathsf{nullifier}$ -is correctly derived: +**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"`. -
- - -### Rationale for 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. -
+See [Why VAN Nullifier Domain Separation]. ##### New VAN Construction -**Condition 6: Proposal authority decrement.** Bit -$\mathsf{proposal}\_\mathsf{id}$ is cleared in the authority bitmask: +**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}}$ is decomposed into 16 boolean - wires $b_0, \ldots, b_{15}$ that recompose to the original value. -- The bit at position $\mathsf{proposal}\_\mathsf{id}$ is asserted to be 1 - (the voter has authority for this proposal). -- $\mathsf{proposal}\_{\mathsf{authority}\_\mathsf{new}}$ is the recomposition with bit - $\mathsf{proposal}\_\mathsf{id}$ cleared; all other bits are unchanged. +- $\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 new VAN is correctly -constructed (using the two-layer construction defined in -[Vote Authority Note (VAN)]): +**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 reuses the old VAN's diversified address and commitment +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.** +**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.** Each share is bounded: +**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\}$$ @@ -834,26 +823,28 @@ 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 baby-step giant-step recovery at tally time. -**Condition 10: Shares hash integrity.** The blinded share commitments -and their aggregate hash are correctly computed: +**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}\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.** Each ciphertext is a -valid encryption of its share under $\mathsf{ea}\_\mathsf{pk}$: +**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 constrains equality on the $x$-coordinates of the computed -and witnessed ciphertext points. Constraining only $x$-coordinates is -sufficient because the shared randomness $r_i$ binds both curve points, -leaving no prover freedom in the $y$-coordinates. +The circuit MUST constrain equality on the $x$-coordinates of the +computed and witnessed ciphertext points. Constraining only +$x$-coordinates is sufficient because the shared randomness $r_i$ binds +both curve points, leaving no prover freedom in the $y$-coordinates. -**Condition 12: Vote commitment integrity.** The public vote commitment -matches the private vote details: +**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)$$ @@ -1276,6 +1267,21 @@ would require securely persisting each component ($\mathsf{vsk}$, $\mathsf{vsk.nk}$, $\mathsf{rivk}\_\mathsf{v}$) 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 From 1dc4d126d925f66530ef987b8482fb5b66c8937a Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 17 Mar 2026 23:25:53 -0300 Subject: [PATCH 42/56] more spec edits and dupl reduction with other ZIPs Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 179 +++++++---------------- 1 file changed, 51 insertions(+), 128 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index bfb58bc53..d79b54e65 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -72,7 +72,7 @@ Submission server : An untrusted server to which a voter delegates the construction and submission of Vote Reveal Proofs. The server learns encrypted shares and vote decisions but cannot decrypt share amounts or link shares to - voter identities. + voter identities. See [^submission-server]. Governance nullifier @@ -185,7 +185,8 @@ 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. +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 @@ -246,9 +247,9 @@ 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. Each payload -contains the data necessary for the server to construct a Vote Reveal -Proof. +an independent payload to one or more submission +servers [^submission-server]. Each payload contains the data necessary +for the server to construct a Vote Reveal Proof. **Phase 4: Share reveal.** Each submission server constructs a Vote Reveal Proof (proving the share belongs to a valid VC in the VCT without @@ -897,7 +898,8 @@ 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. +submission server, not the voter. The Vote Reveal Proof circuit MUST +enforce all conditions specified below. #### Public Inputs @@ -935,13 +937,14 @@ The prover (submission server) knows: ##### Vote Commitment Membership -**Condition 1: Merkle tree membership.** The VC exists in the VCT: -$(\mathsf{path}^{\mathsf{vct}}, \mathsf{pos}^{\mathsf{vct}})$ is a valid Merkle path -from $\mathsf{vc}$ to $\mathsf{rt}^{\mathsf{vct}}$, without revealing which -leaf. The VC value is a private witness. +**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 VC is correctly -constructed from its components: +**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)$$ @@ -951,36 +954,37 @@ revealed share is attributed to the correct proposal and decision. ##### Share Opening -**Condition 3: Shares hash integrity.** The $\mathsf{shares}\_\mathsf{hash}$ is -recomputed from the witness share commitments: +**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}$ is constrained equal to the one -inside the VC (via condition 2). The share commitments are blinded +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 commitment derived from the -public $x$-coordinates $C_{1,x}, C_{2,x}$ and the witness blind -factor matches the share commitment at position +**Condition 4: Share membership.** The circuit MUST enforce that the +commitment derived from the public $x$-coordinates $C_{1,x}, C_{2,x}$ +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}\bigr) = \mathsf{share}\_\mathsf{comms}[\mathsf{share}\_\mathsf{index}]$$ -The circuit encodes $\mathsf{share}\_\mathsf{index}$ as a one-hot selector -vector over $N_s$ positions. The mux extracts the corresponding -$\mathsf{share}\_\mathsf{comm}$ via a dot product and constrains 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. +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 public -$\mathsf{share}\_\mathsf{nullifier}$ is correctly derived: +**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)$$ @@ -1050,114 +1054,33 @@ 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]). - -## Submission Server - -The submission server is an untrusted party that constructs Vote Reveal -Proofs on behalf of voters. Delegating proof construction to a -server provides two benefits: - -- **Reliability.** A mobile client may be killed or lose connectivity - during proof generation. Servers are always-on. -- **Temporal unlinkability.** If a voter submitted all $N_s$ shares - directly, an observer could link them by timing. Servers stagger - submissions at randomized delays across the voting window, mixing - shares from many voters. - -For each payload received, the server: - -1. Waits a randomized delay. -2. Obtains the current VCT Merkle path for the VC. -3. Derives the share nullifier. -4. Constructs the Vote Reveal Proof. -5. Submits the share reveal transaction to the vote chain. - -**What the server learns:** the encrypted share ciphertext and blind -factor for a single share, the blinded share commitments for all $N_s$ -shares, the proposal identifier, and the vote decision. **What it -cannot learn:** the plaintext share amount (El Gamal encrypted under -$\mathsf{ea}\_\mathsf{pk}$), the ciphertexts or blind factors of other -shares (hidden behind blinded commitments), the voter's identity (the -VC hides the link), or which VCT leaf the VC corresponds to (the -Merkle path is a private witness in the proof). - -Voters MAY distribute shares across multiple independent servers to +Voters MUST distribute shares across multiple independent servers to further limit any single server's view of their voting activity. -Specification of server selection, communication protocols, and timing -parameters is deferred to the operational voting process ZIP. - - -## Homomorphic Tally - -After the voting window closes, the tally proceeds in two steps. - -**Step 1: Public aggregation.** For each -$(\mathsf{proposal}\_\mathsf{id}, \mathsf{vote}\_\mathsf{decision})$ pair, the aggregate -ciphertext is the component-wise sum of all revealed shares attributed -to that pair: - -$$\mathsf{agg} = \Bigl(\sum C_{1,j}, \sum C_{2,j}\Bigr)$$ - -This is publicly computable from on-chain data. Any party can -independently verify the aggregation. - -**Step 2: Threshold decryption and public verification.** At least $t$ -validators produce partial decryptions that are combined via Lagrange -interpolation to recover $\mathsf{total}\_\mathsf{ballots}$. The procedure -for partial decryption submission and Lagrange combination is specified -in [^ea-ceremony]. - -Each validator $i$ computes $D_i = [s_i]\, C_1^{\mathsf{agg}}$ using -their Shamir share $s_i$ and submits $D_i$ along with a -Chaum-Pedersen DLEQ proof demonstrating correct share usage (see -[Partial Decryption Proof]). A verifier MUST check each per-validator -proof against the validator's published verification key -$\mathsf{VK}\_i$ and $C_1^{\mathsf{agg}}$ before accepting $D_i$ for -Lagrange combination. - -The combined result MUST be publicly verified. Given the aggregate -ciphertext $\mathsf{agg} = (C_1^{\mathsf{agg}}, C_2^{\mathsf{agg}})$ and -the Lagrange-combined partial decryption point $D_{\mathsf{combined}}$, -any party can check: - -$$C_2^{\mathsf{agg}} - D_{\mathsf{combined}} = [\mathsf{total}\_\mathsf{ballots}]\, G$$ - -When the full election authority secret key is available (e.g., for -testing), correct decryption MAY -alternatively be proven via the aggregate variant of the DLEQ proof -(see [Aggregate Decryption Proof]). - -Individual vote amounts are never revealed; only the aggregate total per -(proposal, decision) pair. +Server selection, temporal mixing, and communication protocols are +specified in [^submission-server]. -## Election Authority Key +## Tally -Each voting round uses a fresh election authority keypair -$(\mathsf{ea}\_\mathsf{sk}, \mathsf{ea}\_\mathsf{pk})$ produced by the key -ceremony specified in [^ea-ceremony]. After the ceremony, no single -party holds $\mathsf{ea}\_\mathsf{sk}$; each validator holds only a Shamir -share. At tally time, at least $t = \lceil n/2 \rceil + 1$ validators -cooperate to produce partial decryptions without reconstructing the -full secret key. +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 is a purpose-built chain that records all governance -transactions. It maintains: +The vote chain MUST maintain the following state per voting round: -- The **Vote Commitment Tree**: an append-only Poseidon Merkle tree - storing VANs and VCs. -- Three **nullifier sets**: governance, VAN, and share nullifiers. -- An **encrypted share accumulator**: per - $(\mathsf{proposal}\_\mathsf{id}, \mathsf{vote}\_\mathsf{decision})$, the running - component-wise sum of revealed El Gamal ciphertexts. +- 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 chain verifies the corresponding proof, -checks nullifier freshness, updates the VCT, and (for share reveals) -accumulates the ciphertext. All verification is ZKP-based - the chain -never observes plaintext vote amounts or voter identities. +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. @@ -1564,7 +1487,7 @@ finalization of this ZIP. [^voting-setup]: [Zcash Shielded Coinholder Voting](draft-valargroup-shielded-voting-setup) -[^chaum-pedersen]: [D. Chaum and T. P. Pedersen, "Wallet Databases with Observers", CRYPTO 1992](https://link.springer.com/chapter/10.1007/3-540-48071-4_7) +[^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) From d2df6b4f42dcf33e77cd5a98527eef9ee2202a3f Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 17 Mar 2026 23:43:24 -0300 Subject: [PATCH 43/56] add implementations and reduce duplication Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 51 +++++++++++------------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index d79b54e65..168e0f81d 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -1085,26 +1085,6 @@ corresponding proof and perform the out-of-circuit checks specified in The vote chain's consensus mechanism, block structure, transaction encoding, and API are out of scope for this ZIP. -## Chaum-Pedersen DLEQ Proof - -The protocol uses a non-interactive Chaum-Pedersen proof of -discrete-log equality (DLEQ) [^chaum-pedersen] to verify correct usage -of secret key material during decryption. The DLEQ proof construction, including the statement, proof generation, proof verification, and -Fiat-Shamir challenge derivation with the `"svote-dleq-v1"` domain -tag, is defined in [^ea-ceremony]. A proof is a pair of Pallas scalars -$(e, z)$ demonstrating $\log_G(P) = \log_H(Q)$. - -### Partial Decryption Proof - -In threshold mode, each validator $i$ proves that their partial -decryption was computed using their correct Shamir share $s_i$. The -DLEQ proof instantiation for partial decryption — with -$P = \mathsf{VK}\_i = [s_i]\, G$, -$H = C_1^{\mathsf{agg}}$, -$Q = D_i = [s_i]\, C_1^{\mathsf{agg}}$, and -$x = s_i$ — is defined in [^ea-ceremony]. - - # Rationale ## Why a Separate Vote Chain @@ -1422,18 +1402,20 @@ pre-firmware circuit (see [Open issues]). # Reference implementation -A reference implementation of the three ZKP circuits (Delegation Proof, -Vote Proof, and Vote Reveal Proof) and the vote chain state machine -exists in the coinholder voting codebase used for this design. - -At the time of writing, some implementation repositories are not -publicly accessible. Public, stable links will be added before -finalization of this ZIP. +- [^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 -- Open issues related to the EA key ceremony are tracked in [^ea-ceremony]. - Hardware wallet firmware with voting-aware signing (e.g., a governance network byte) would allow a simplified Delegation Proof circuit that removes the dummy signed note scaffolding (signed note @@ -1463,6 +1445,11 @@ finalization of this ZIP. updating the Merkle membership circuits in both the Vote Proof and Vote Reveal Proof, reproving the verification key, and coordinating a CometBFT chain upgrade. See [Why VCT Depth 24]. +- 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 @@ -1498,3 +1485,11 @@ finalization of this ZIP. [^protocol-concretecommitivk]: [Zcash Protocol Specification, Version 2025.6.3 [NU6.1]. Section 5.4.9.4: CommitIvk](protocol/protocol.pdf#concretecommitivk) [^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) From 1572153dceb2716845362b4902a64735657a2dab Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 17 Mar 2026 23:45:29 -0300 Subject: [PATCH 44/56] define governance hotkey Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 168e0f81d..b644fcc5e 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -74,6 +74,12 @@ Submission server and vote decisions but cannot decrypt share amounts or link shares to voter identities. See [^submission-server]. +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 From 7ab5e855997eff5bf3208f10e04894f813fad3c7 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 17 Mar 2026 23:47:08 -0300 Subject: [PATCH 45/56] avoid definitions under terminology Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index b644fcc5e..67d1792ba 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -63,16 +63,12 @@ Voting round 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. No single - validator holds the full key. At tally time, $t$ validators cooperate - to produce partial decryptions of the aggregate ciphertext. + 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. The server learns encrypted shares - and vote decisions but cannot decrypt share amounts or link shares to - voter identities. See [^submission-server]. + submission of Vote Reveal Proofs. Governance hotkey : An Orchard key hierarchy, distinct from the holder's spending key, From a831603ddc1d56113d4522fe0fbe89793b419f06 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 18 Mar 2026 00:18:10 -0300 Subject: [PATCH 46/56] reference keystone zip and add sig hash spec Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 159 ++++++++++++++--------- 1 file changed, 101 insertions(+), 58 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 67d1792ba..472cce993 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -657,15 +657,37 @@ 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 $\sigma$ as a valid $\mathsf{SpendAuthSig}^{\mathsf{Orchard}}$ - signature on the application-defined sighash, under $\mathsf{rk}$. -3. Verify that $\mathsf{rt}^{\mathsf{cm}}$ and $\mathsf{rt}^{\mathsf{excl}}$ correspond +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}}$. -4. Verify that no $\mathsf{gov}\_{\mathsf{null}\_\mathsf{i}}$ appears in the governance +5. Verify that no $\mathsf{gov}\_{\mathsf{null}\_\mathsf{i}}$ appears in the governance nullifier set. If any does, reject as a double-delegation. -5. Verify that $\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}$ matches an active round. -6. Insert $\mathsf{van}$ into the VCT. -7. Add all $\mathsf{gov}\_{\mathsf{null}\_\mathsf{i}}$ to the governance nullifier set. +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 for Keystone devices is specified +in [^keystone-voting]. 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 @@ -675,6 +697,7 @@ A delegation transaction submitted to the vote chain MUST contain: |---|---|---| | $\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 | @@ -858,20 +881,54 @@ spend authorization signature $\sigma$ MUST perform the following checks: 1. Verify $\pi$ against the public inputs. -2. Verify $\sigma$ as a valid $\mathsf{SpendAuthSig}^{\mathsf{Orchard}}$ - signature on the application-defined sighash, under - $\mathsf{r}\_\mathsf{vpk}$. -3. Verify that $\mathsf{van}\_\mathsf{nullifier}$ does not appear in the VAN +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. -4. Verify that $\mathsf{rt}^{\mathsf{vct}}$ matches a published VCT root at +5. Verify that $\mathsf{rt}^{\mathsf{vct}}$ matches a published VCT root at $\mathsf{anchor}\_\mathsf{height}$. -5. Verify that $\mathsf{proposal}\_\mathsf{id}$ is valid for the current round +6. Verify that $\mathsf{proposal}\_\mathsf{id}$ is valid for the current round and within the voting window. -6. Verify that $\mathsf{voting}\_{\mathsf{round}\_\mathsf{id}}$ matches an active round. -7. Verify that $\mathsf{ea}\_\mathsf{pk}$ matches the published election +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. -8. Insert $\mathsf{van}_{\mathsf{new}}$ and $\mathsf{vc}$ into the VCT. -9. Add $\mathsf{van}\_\mathsf{nullifier}$ to the VAN nullifier set. +9. Insert $\mathsf{van}_{\mathsf{new}}$ and $\mathsf{vc}$ into the VCT. +10. Add $\mathsf{van}\_\mathsf{nullifier}$ to the VAN nullifier set. + +### 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 @@ -1205,29 +1262,19 @@ cost in prover resources. Holders with more than 5 notes can perform multiple de 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 (e.g., Keystone) that support only the standard Orchard PCZT -signing flow. - -Without voting-specific firmware, the hardware wallet interprets the -delegation as a standard Orchard Action and signs the transaction -sighash accordingly. The dummy note's rho binding ensures this signature -is non-replayable and scoped to the exact delegation context (all note -commitments, the VAN, and the voting round), even though the hardware -wallet is unaware of governance semantics. The sighash that the hardware -wallet signs commits to a structure that appears to be a fund-moving -transaction - this is an inherent consequence of reusing the standard -signing flow and cannot be avoided without firmware changes. - -When hardware wallet firmware adds voting-aware signing (e.g., a -governance network byte analogous to the testnet byte), the firmware can -display the delegation context to the user (notes, amounts, voting -round) and sign a governance-specific sighash that binds directly to -the delegation parameters. The signed note scaffolding (signed note -integrity, signed note nullifier, rho binding, and output note -commitment) can then be removed from the circuit. This migration is -purely subtractive: the simplified circuit is a strict subset of the -current one, and only the Delegation Proof changes. The Vote Proof -and Vote Reveal Proof are unaffected. +wallets that support only the standard Orchard PCZT signing flow, +without requiring firmware changes specific to governance. The dummy +note's rho binding ensures the sighash transitively commits to the +delegation context (all note commitments, the VAN, and the voting +round), even though the hardware wallet is unaware of governance +semantics. + +The governance PCZT construction, signing flow, and rationale for +specific design choices (1-zatoshi display value, rho binding for +non-replayability, ZIP 244 sighash reuse) are specified +in [^keystone-voting]. When hardware wallet firmware adds voting-aware +signing, the signed note scaffolding can be removed from the circuit; +this migration path is also discussed in [^keystone-voting]. ## Why $N_s$ Shares Per Vote @@ -1388,18 +1435,11 @@ 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. -The protocol is designed to support two hardware wallet modes for the -delegation phase. In the pre-firmware mode, the spend authorization -signature is obtained through a standard PCZT-based signing flow: the -hardware wallet signs what it interprets as an Orchard Action, and the -dummy signed note mechanism (see [Why a Dummy Signed Note]) ensures -correctness. In the post-firmware mode, the hardware wallet recognizes -a governance-specific network byte, displays the delegation context -(notes, amounts, voting round) to the user, and signs a -governance-specific sighash. Both modes produce a valid -$\mathsf{SpendAuthSig}^{\mathsf{Orchard}}$ that the Delegation Proof -consumes. The post-firmware circuit is a strict subset of the -pre-firmware circuit (see [Open issues]). +The delegation phase supports hardware wallets via a governance PCZT +that reuses the standard Orchard signing flow. The PCZT construction, +signing interaction, and device display are specified +in [^keystone-voting] for Keystone devices. Software wallets sign the +delegation sighash directly without PCZT construction. # Reference implementation @@ -1418,12 +1458,9 @@ pre-firmware circuit (see [Open issues]). # Open issues -- Hardware wallet firmware with voting-aware signing (e.g., a - governance network byte) would allow a simplified Delegation Proof - circuit that removes the dummy signed note scaffolding (signed note - integrity, rho binding, output note commitment). The migration is - purely subtractive: the post-firmware circuit is a strict subset of - the pre-firmware circuit. See [Why a Dummy Signed Note]. +- Voting-aware hardware wallet firmware would allow a simplified + Delegation Proof circuit that removes the dummy signed note + scaffolding. See [^keystone-voting] for the migration path. - 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 @@ -1474,6 +1511,8 @@ pre-firmware circuit (see [Open issues]). [^ea-ceremony]: [Election Authority Key Ceremony](draft-valargroup-ea-key-ceremony) +[^keystone-voting]: [Keystone Hardware Wallet Voting Delegation](draft-valargroup-keystone-voting) + [^voting-setup]: [Zcash Shielded Coinholder Voting](draft-valargroup-shielded-voting-setup) [^submission-server]: [Vote Share Submission Server](draft-valargroup-submission-server) @@ -1486,6 +1525,10 @@ pre-firmware circuit (see [Open issues]). [^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) From 5c6b35bce346f2451e271585e9c981b188577fc5 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 18 Mar 2026 00:24:02 -0300 Subject: [PATCH 47/56] poseidon Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 38 +++++++++++++++++++++--- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 472cce993..3ced7e47d 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -416,10 +416,9 @@ Proof and added to the share nullifier set to prevent double-counting. The VCT MUST be an incremental Merkle tree [^protocol-merkletree] of depth $\mathsf{MerkleDepth}^{\mathsf{vct}} = 24$ that stores both VANs and VCs as leaves. It MUST use the same append-only data structure as -the Orchard note commitment tree, but with Poseidon [^poseidon] over -the Pallas scalar field for internal node hashing instead of Sinsemilla, -using the same instantiation as the nullifier non-membership -tree [^balance-proof]. +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 @@ -1144,6 +1143,37 @@ corresponding proof and perform the out-of-circuit checks specified in 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) for +$L \leq 7$. The shares hash ($L = N_s = 16$) uses variable-length +sponge absorption without length padding, absorbing two elements per +permutation. + +The following table lists every Poseidon call site in this protocol: + +| Hash | Inputs ($L$) | Mode | Permutations | +|---|---|---|---| +| 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 | 3 | $\mathsf{ConstantLength}\langle 3 \rangle$ | 2 | +| Shares hash | 16 | Sponge | 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 | + + # Rationale ## Why a Separate Vote Chain From 07828e96e5ac9034b15805074534002c2a15bd2e Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 18 Mar 2026 00:34:55 -0300 Subject: [PATCH 48/56] per-vote secret derivation and edits Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 108 ++++++++++++++++------- 1 file changed, 77 insertions(+), 31 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 3ced7e47d..a156f6a18 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -200,7 +200,7 @@ see [^pir-governance]. - A holder's on-chain identity (spending key, standard nullifiers) is not linkable to their voting activity. -- 1 user-facing signatures per 5 notes. +- The number of user-facing signatures is minimized. Over 90% of wallets require 1 user-facing signature in our system. The rest may require more signatures. - 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 @@ -230,9 +230,9 @@ see [^pir-governance]. specified separately in [^pir-governance]. -# Specification +# High-level summary -## Protocol Overview +This section is non-normative. The protocol proceeds in five phases within a voting round. @@ -261,12 +261,15 @@ 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 bounded -discrete-log search). Correctness is publicly verifiable: anyone can -recompute the Lagrange combination from the on-chain partial -decryptions. +(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 @@ -439,19 +442,6 @@ The vote chain MUST maintain three disjoint nullifier sets: 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. -### 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. The protocol defines three tags: @@ -509,6 +499,46 @@ $\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 @@ -846,7 +876,7 @@ $$0 \leq \mathsf{v}\_\mathsf{i} < 2^{30} \quad \text{for each } i \in \{0 \ldots 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 baby-step giant-step recovery at tally time. +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 @@ -886,8 +916,9 @@ checks: 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{rt}^{\mathsf{vct}}$ matches a published VCT root at - $\mathsf{anchor}\_\mathsf{height}$. +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. @@ -1173,6 +1204,19 @@ The following table lists every Poseidon call site in this protocol: | 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 @@ -1203,7 +1247,8 @@ non-membership tree in [^balance-proof]. 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 -bounded discrete-log search at tally time has a smaller search space. +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. @@ -1245,18 +1290,19 @@ voting protocol. 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. Additionally, the El Gamal -encryption randomness and per-share blind factors used in the Vote -Proof are derived deterministically from the spending key via a -domain-separated PRF, so crash recovery extends to per-vote secrets -as well. +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}$) independently, +$\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 From ee52190c38a0e5c8ac4ccc8a6e5fa5269c0dde04 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 18 Mar 2026 00:41:36 -0300 Subject: [PATCH 49/56] cmx_new note Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index a156f6a18..7aecaaaae 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -597,7 +597,8 @@ Given a primary input: 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). + commitment to the governance hotkey address (PCZT scaffolding; not + inserted into any on-chain tree). #### Auxiliary Inputs @@ -610,9 +611,10 @@ in [^balance-proof]: - 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: $\mathsf{g}\_\mathsf{d}^{\mathsf{new}}, \mathsf{pk}\_\mathsf{d}^{\mathsf{new}}, +- 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 @@ -663,7 +665,17 @@ $(\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. +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 [^keystone-voting] 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, From 8dda70c28a7c8343aa76c6d8d93d6bf0979f6a56 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 18 Mar 2026 00:55:38 -0300 Subject: [PATCH 50/56] implementation alignment and edits Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 64 ++++++++++++++++-------- 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 7aecaaaae..09315c76f 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -306,10 +306,12 @@ where: - $\mathsf{DOMAIN}\_\mathsf{VAN} = 0$ — domain tag distinguishing VANs from VCs in the shared VCT. -- $\mathsf{vpk}\_{\mathsf{g}\_\mathsf{d}} \in \mathbb{P}^*$ — diversified base of the +- $\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{vpk}\_{\mathsf{pk}\_\mathsf{d}} \in \mathbb{P}^*$ — 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. @@ -373,7 +375,12 @@ is being opened. 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. +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\}$: @@ -468,11 +475,16 @@ A governance hotkey consists of: 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 - $(\mathsf{vpk}\_{\mathsf{g}\_\mathsf{d}}, \mathsf{vpk}\_{\mathsf{pk}\_\mathsf{d}})$ - at a chosen diversifier index, where - $\mathsf{vpk}\_{\mathsf{pk}\_\mathsf{d}} = [\mathsf{ivk}\_\mathsf{v}]\, \mathsf{vpk}\_{\mathsf{g}\_\mathsf{d}}$ - and $\mathsf{ivk}\_\mathsf{v} = \mathsf{CommitIvk}\_{\mathsf{rivk}\_\mathsf{v}}\!\bigl(\mathsf{Extract}\_{\mathbb{P}}(\mathsf{vsk.ak}),\; \mathsf{vsk.nk}\bigr)$. +- 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 @@ -790,10 +802,11 @@ The prover knows: - $\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 from the - VAN. +- $\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 from the VAN. + 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. @@ -820,7 +833,9 @@ $\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)]): +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)$$ @@ -939,6 +954,15 @@ checks: 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 @@ -1105,8 +1129,10 @@ following checks: 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. Add $\mathsf{share}\_\mathsf{nullifier}$ to the share nullifier set. -7. Accumulate $\mathsf{enc}\_\mathsf{share}$ into the aggregate ciphertext for +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}$$ @@ -1196,10 +1222,8 @@ base field), with S-box $x^5$, width $t = 3$, rate $r = 2$, targeting from [^poseidon]. Hashes with $L$ inputs use $\mathsf{ConstantLength}\langle L \rangle$ -mode (absorbing $L$ field elements with length padding) for -$L \leq 7$. The shares hash ($L = N_s = 16$) uses variable-length -sponge absorption without length padding, absorbing two elements per -permutation. +mode (absorbing $L$ field elements with length padding), absorbing +two elements per permutation. The following table lists every Poseidon call site in this protocol: @@ -1210,7 +1234,7 @@ The following table lists every Poseidon call site in this protocol: | VAN nullifier | 4 | $\mathsf{ConstantLength}\langle 4 \rangle$ | 2 | | Vote commitment | 5 | $\mathsf{ConstantLength}\langle 5 \rangle$ | 3 | | Blinded share commitment | 3 | $\mathsf{ConstantLength}\langle 3 \rangle$ | 2 | -| Shares hash | 16 | Sponge | 8 | +| 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 | From 4f1355a4f8205931d23256dd72e9077cccc87d37 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 18 Mar 2026 01:34:01 -0300 Subject: [PATCH 51/56] fix requirements Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 09315c76f..a8c6f1905 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -200,16 +200,14 @@ see [^pir-governance]. - A holder's on-chain identity (spending key, standard nullifiers) is not linkable to their voting activity. -- The number of user-facing signatures is minimized. Over 90% of wallets require 1 user-facing signature in our system. The rest may require more signatures. -- No double-delegation for the same note within a voting round -- No double voting for the same voting share within the same proposal +- 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 protocol supports up to 15 proposals per voting round. -- The Vote Commitment Tree has capacity for 2^24 = 16.7 million - leaves (combined VANs and VCs) per voting round. - 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. From 30e4391de3f293087e214512f4cc4e078b3c4c3b Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 19 Mar 2026 23:06:11 -0300 Subject: [PATCH 52/56] bind y coordinate Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 64 +++++++++++++++++------- 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index a8c6f1905..376c1515c 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -392,10 +392,11 @@ For share index $i \in \{0 \ldots N_s - 1\}$: **Blinded share commitment:** -$$\mathsf{share}\_{\mathsf{comm}\_\mathsf{i}} = \mathsf{Poseidon}(\mathsf{blind}\_\mathsf{i}, C_{1,i,x}, C_{2,i,x})$$ +$$\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}$ and $C_{2,i,x}$ denote the $x$-coordinates of the -ciphertext points. +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 @@ -907,7 +908,7 @@ small enough for efficient recovery at tally time (see [^ea-ceremony]). 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}\bigr) \quad \text{for each } i$$ +$$\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)$$ @@ -918,10 +919,12 @@ $\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 the $x$-coordinates of the -computed and witnessed ciphertext points. Constraining only -$x$-coordinates is sufficient because the shared randomness $r_i$ binds -both curve points, leaving no prover freedom in the $y$-coordinates. +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: @@ -1030,10 +1033,9 @@ Given a primary input: - $\mathsf{share}\_\mathsf{nullifier} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ — prevents double-counting. -- $C_{1,x}, C_{2,x} ⦂ \{ 0 .. q_{\mathbb{P}}-1 \}$ — the $x$-coordinates - of the El Gamal ciphertext $(C_1, C_2)$ for this share. The full - points are carried in the share reveal message for homomorphic - accumulation, but only the $x$-coordinates are circuit public inputs. +- $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 @@ -1089,11 +1091,12 @@ the VC (via condition 2). The share commitments are blinded 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 $x$-coordinates $C_{1,x}, C_{2,x}$ -and the witness blind factor matches the share commitment at position +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}\bigr) = \mathsf{share}\_\mathsf{comms}[\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 @@ -1231,7 +1234,7 @@ The following table lists every Poseidon call site in this protocol: | 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 | 3 | $\mathsf{ConstantLength}\langle 3 \rangle$ | 2 | +| 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 | @@ -1398,13 +1401,38 @@ sees only individual shares, not a voter's complete ballot allocation. ## 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})$. +$\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})$ for each on-chain ciphertext +$\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 From 35b877a8823ab08d2d9bb6ab99122cede9a6358f Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 19 Mar 2026 23:43:05 -0300 Subject: [PATCH 53/56] vct per round update Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 29 +++++++++++------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 376c1515c..ea550dde6 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -45,7 +45,8 @@ Vote Commitment (VC) Vote Commitment Tree (VCT) : An append-only Poseidon Merkle tree maintained by the vote chain that - stores both VANs and VCs as leaves. + stores both VANs and VCs as leaves. A separate VCT is maintained per + voting round; trees from different rounds are fully isolated. Vote share @@ -422,12 +423,14 @@ Proof and added to the share nullifier set to prevent double-counting. ### Vote Commitment Tree -The VCT MUST be an incremental Merkle tree [^protocol-merkletree] of -depth $\mathsf{MerkleDepth}^{\mathsf{vct}} = 24$ that stores both VANs -and VCs as leaves. It 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]). +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 @@ -1549,7 +1552,9 @@ The Orchard note commitment tree uses depth 32, supporting $2^{32}$ 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. +(~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 @@ -1614,14 +1619,6 @@ delegation sighash directly without PCZT construction. to track VCT paths (e.g., full server-side path retrieval), this tradeoff should be revisited. See [Why a Send-Based VAN Model]. -- The VCT depth of 24 ($2^{24} \approx 16.7$ million leaves) is - sufficient for current governance usage projections, but may need to - be increased via a vote chain upgrade if participation grows - significantly (e.g., more voters, more proposals per round, or more - frequent rounds reusing the same tree). Increasing the depth requires - updating the Merkle membership circuits in both the Vote Proof and - Vote Reveal Proof, reproving the verification key, and coordinating - a CometBFT chain upgrade. See [Why VCT Depth 24]. - 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 From d9a2b228cb5c8f9ea9239c79ac7cce8661a224cf Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 20 Mar 2026 00:06:38 -0300 Subject: [PATCH 54/56] reduce duplication Co-authored-by: Claude --- zips/draft-valargroup-shielded-voting.md | 39 +++++++++--------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index ea550dde6..76b000257 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -688,7 +688,7 @@ The vote chain SHOULD NOT insert $\mathsf{cmx}\_\mathsf{new}$ into any commitmen or use it beyond proof verification; it serves only to make the hardware wallet's signed sighash commit to a complete Action structure. -See [^keystone-voting] for the PCZT construction details and +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 @@ -729,8 +729,8 @@ 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 for Keystone devices is specified -in [^keystone-voting]. For software wallets, the client signs 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 @@ -1379,18 +1379,12 @@ 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 -note's rho binding ensures the sighash transitively commits to the -delegation context (all note commitments, the VAN, and the voting -round), even though the hardware wallet is unaware of governance -semantics. - -The governance PCZT construction, signing flow, and rationale for -specific design choices (1-zatoshi display value, rho binding for -non-replayability, ZIP 244 sighash reuse) are specified -in [^keystone-voting]. When hardware wallet firmware adds voting-aware -signing, the signed note scaffolding can be removed from the circuit; -this migration path is also discussed in [^keystone-voting]. +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 @@ -1578,11 +1572,7 @@ 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. -The delegation phase supports hardware wallets via a governance PCZT -that reuses the standard Orchard signing flow. The PCZT construction, -signing interaction, and device display are specified -in [^keystone-voting] for Keystone devices. Software wallets sign the -delegation sighash directly without PCZT construction. +construction. # Reference implementation @@ -1601,9 +1591,10 @@ delegation sighash directly without PCZT construction. # Open issues -- Voting-aware hardware wallet firmware would allow a simplified - Delegation Proof circuit that removes the dummy signed note - scaffolding. See [^keystone-voting] for the migration path. +- 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 @@ -1646,8 +1637,6 @@ delegation sighash directly without PCZT construction. [^ea-ceremony]: [Election Authority Key Ceremony](draft-valargroup-ea-key-ceremony) -[^keystone-voting]: [Keystone Hardware Wallet Voting Delegation](draft-valargroup-keystone-voting) - [^voting-setup]: [Zcash Shielded Coinholder Voting](draft-valargroup-shielded-voting-setup) [^submission-server]: [Vote Share Submission Server](draft-valargroup-submission-server) From 8e5a5e65138f2ab61fd6f6a2b40bd73beb3bb989 Mon Sep 17 00:00:00 2001 From: Adam Tucker Date: Thu, 19 Mar 2026 21:22:47 -0600 Subject: [PATCH 55/56] Update voting protocol ZIP for client-controlled timing and single-share mode - Phase 3/4 summary: client-chosen submit_at, last-moment single-share - Share Submission Payload: add submit_at field - Why N_s Shares: add single-share last-moment exception and rationale --- zips/draft-valargroup-shielded-voting.md | 33 +++++++++++++++++------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 76b000257..9d0cf27a7 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -249,14 +249,18 @@ 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 contains the data necessary -for the server to construct a Vote Reveal Proof. +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 a randomized -delay. The chain accumulates the revealed El Gamal ciphertexts -homomorphically. +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 @@ -1176,6 +1180,7 @@ payload. For share $i$, the payload MUST contain: | $\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$ @@ -1390,10 +1395,20 @@ protocol) are specified in [^balance-proof]. Splitting a vote into $N_s$ shares serves two purposes. First, it provides temporal unlinkability: shares are submitted independently at -randomized times, 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. +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 From 7b1ef40df9ec85242f725e02073635c2c118d9ae Mon Sep 17 00:00:00 2001 From: Greg Nagy Date: Fri, 10 Apr 2026 15:34:50 +0200 Subject: [PATCH 56/56] Specify the voting round identifier derivation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The voting round identifier was used as an opaque Pallas scalar input to several Poseidon constructions in this ZIP (van_core, vc, van_nullifier, gov_null_i, ρ_signed, the Vote Reveal Proof public inputs) without ever being defined. Specify it as a deterministic Poseidon hash over the eight setup-field inputs that uniquely characterise a round. Place the new subsection at the top of the Data Structures section, before Vote Authority Note, since voting_round_id is the most foundational data structure: every other on-chain construction in this ZIP takes it as an input. Reference the canonical Poseidon Instantiation section for the parameter set and add a row for the new hash to its inventory table. Co-Authored-By: Claude Opus 4.6 (1M context) --- zips/draft-valargroup-shielded-voting.md | 43 ++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/zips/draft-valargroup-shielded-voting.md b/zips/draft-valargroup-shielded-voting.md index 9d0cf27a7..10dff02ae 100644 --- a/zips/draft-valargroup-shielded-voting.md +++ b/zips/draft-valargroup-shielded-voting.md @@ -290,6 +290,48 @@ $\mathsf{Enc}(v, r) = \bigl([r]\, G,\; [v]\, G + [r]\, \mathsf{ea}\_\mathsf{pk}\ ## 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 @@ -1238,6 +1280,7 @@ 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 |