Governance ZKP circuits (delegation, vote proof, share reveal) for the Zcash shielded-voting protocol.
Built with halo2 on top of the upstream
orchard implementation shared by Orchard
and Ironwood. The crate requires std.
Ironwood Notes ──► Delegation (ZKP 1) ──► Vote Authority Notes (VANs)
│
▼
Vote Proof (ZKP 2) ──► Vote Commitments + encrypted shares
│
▼
Share Reveal (ZKP 3) ──► Revealed shares for tally
- Delegation spends Ironwood V3 notes and mints VANs that carry delegated voting weight.
- Vote Proof spends a VAN to cast a vote, producing El Gamal-encrypted shares and a vote commitment.
- Share Reveal opens a single encrypted share and proves it belongs to a registered vote commitment.
This crate is the circuit-only side. Wallets typically don't call it directly; they consume the higher-level zcash_voting crate which wraps proof generation, hotkey derivation, share construction, and the HTTP wire format.
If you do want the raw gadgets for a custom prover:
use voting_circuits::vote_proof::Circuit as VoteProofCircuit;
// ... assemble public/private inputs and run halo2_proofsMinimum supported Rust version: 1.86, as declared by the crate manifest.
Protocol domain-separation tags are registered in src/domain_tags.rs. Hash-owning modules document their own preimage layout, but new tags should be added to the registry first so the encoding rule and distinctness test stay centralized.
src/
├── lib.rs # Crate root — re-exports the three circuits
├── circuit/ # Shared gadgets used across circuits
│ ├── address_ownership.rs # CommitIvk + diversified-address integrity
│ ├── elgamal.rs # El Gamal encryption (vote proof condition 11)
│ ├── poseidon_merkle.rs # Poseidon-based Merkle path verification
│ ├── van_integrity.rs # VAN commitment hash (two-layer Poseidon)
│ └── vote_commitment.rs # Vote commitment hash
├── shares_hash.rs # Shares-hash gadget (shared by ZKP 2 & 3)
│
├── delegation/ # ZKP #1 — Delegation circuit (K=14)
│ ├── circuit.rs # 15-condition halo2 circuit
│ ├── builder.rs # Multi-note bundle builder (up to 5 notes)
│ ├── prove.rs # Prove / verify helpers
│ ├── imt.rs # Indexed Merkle Tree (data structure)
│ ├── imt_circuit.rs # IMT non-membership proof gadget
│ └── README.md # Detailed specification
│
├── vote_proof/ # ZKP #2 — Vote Proof circuit (K=13)
│ ├── circuit.rs # 12-condition halo2 circuit
│ ├── builder.rs # Builder producing VoteProofBundle
│ ├── prove.rs # Prove / verify helpers
│ ├── authority_decrement.rs # Proposal-authority decrement gadget
│ └── README.md # Detailed specification
│
└── share_reveal/ # ZKP #3 — Share Reveal circuit (K=11)
├── circuit.rs # 5-condition halo2 circuit
├── builder.rs # Builder
└── prove.rs # Prove / verify helpers
benches/
└── delegation.rs # Criterion benchmarks for delegation proving
Reusable halo2 gadgets that appear in more than one circuit:
| Gadget | Used by | Purpose |
|---|---|---|
address_ownership |
Delegation, Vote Proof | CommitIvk + diversified-address binding |
elgamal |
Vote Proof | El Gamal encryption of vote shares |
poseidon_merkle |
All three | Poseidon Merkle-path membership proofs |
van_integrity |
Delegation, Vote Proof | Two-layer Poseidon hash for VAN commitments |
vote_commitment |
Vote Proof, Share Reveal | Hash of (domain, round_id, shares_hash, proposal_id, decision) |
shares_hash (at crate root) computes a two-level Poseidon hash over 16 blinded share commitments and is shared by ZKP 2 and ZKP 3.
| Circuit | K | Rows | Conditions | Spec |
|---|---|---|---|---|
| Delegation | 14 | 16 384 | 15 | ZKP #1 |
| Vote Proof | 13 | 8 192 | 12 | ZKP #2 |
| Share Reveal | 11 | 2 048 | 5 | ZKP #3 |
This crate depends on the upstream orchard 0.15.0 release, with the circuit
and unstable-voting-circuits features enabled to expose the circuit APIs used
by the governance proofs. The delegation bundle builder requires Ironwood V3
notes and constructs its synthetic signed and output notes as V3.
The V3 check is a bundle-construction policy, not a note-version bit in the
Halo2 statement. An Ironwood-only verifier must independently authenticate
nc_root as an Ironwood note commitment tree root and must not accept an
Orchard root supplied by the prover. It must authenticate nf_imt_root at the
same snapshot height.
cargo buildShort-running tests are the default:
cargo testLong-running tests are explicitly ignored and can be run when circuit-level coverage is needed. Skip the row-budget and cost-breakdown diagnostics for a normal regression pass:
cargo test -- --ignored --skip row_budget --skip cost_breakdownTo inspect circuit size diagnostics, keep --nocapture so the output is printed:
cargo test row_budget -- --ignored --nocapture
cargo test cost_breakdown -- --ignored --nocaptureThe long tests are slow because they synthesize Halo 2 circuits and run MockProver verification over the configured K domain (delegation uses K=14, vote_proof K=13, and share_reveal K=11). Some gadget stress tests are also long-running because they repeat many MockProver checks, for example one K=12 shares-hash test runs 16 separate prover checks. The real proof roundtrip also performs proving-key/proof generation and verification, so it is intentionally outside the default unit-test path.
cargo bench # runs delegation proving benchmarks via Criterion| Crate | Role |
|---|---|
halo2_proofs |
Proof system (with batch verification) |
halo2_gadgets |
Standard gadgets (Poseidon, Sinsemilla, ECC) |
pasta_curves |
Pallas / Vesta curve arithmetic |
orchard |
Orchard protocol primitives used by Ironwood, including note commitments, nullifiers, and CommitIvk |
halo2_poseidon |
Poseidon hash for Merkle trees and commitments |
incrementalmerkletree |
Incremental Merkle tree data structure |
sinsemilla |
Sinsemilla hash (used via Orchard) |
Dual-licensed under MIT or Apache-2.0. See LICENSE-MIT and LICENSE-APACHE.