Quadratic Voting on Midnight: A ZK-Powered Governance Model for Securely Updating Smart Contracts
How do you upgrade a decentralized protocol? The naive answer is a multi-sig wallet controlled by the founding team. The idealistic answer is one-person-one-vote. Both are wrong.
Multi-sig is centralization with extra steps. One-person-one-vote ignores the intensity of preference — a stakeholder who barely cares about a proposal has the same power as one whose entire business depends on it. And one-token-one-vote, the most common Web3 model, simply reproduces plutocracy on-chain: whoever holds the most tokens wins every vote.
For a privacy-focused protocol like DPO2U, this is compounded by a second problem: public voting exposes stakeholder positions. If a regulator, a competitor, or a large token holder can see how you voted on a proposal to raise the minimum compliance score, they can apply pressure, retaliate, or game the system. The privacy guarantees of the protocol extend to its governance, or they mean nothing.
This tutorial demonstrates a solution to both problems: Private Quadratic Voting (QV). We build a ZK-powered governance system on the Midnight Network that allows stakeholders to vote on protocol upgrades with the nuance of QV — while preserving their privacy with the power of zk-SNARKs. The result is a governance model that is simultaneously more expressive, more equitable, and more private than anything possible on a transparent blockchain.
The problem: governance of a living protocol
DPO2U's ComplianceRegistry.compact is not a static contract. As LGPD and GDPR enforcement evolves, the protocol must adapt. Security parameters that are adequate today may be insufficient tomorrow. Consider these real governance questions:
- Should the
minComplianceScorethreshold be raised from 80 to 85 in response to new ANPD guidance? - Should the
dpo2u/lgpd/v1schema version be deprecated in favor ofv2? - Should the required number of auditor signatures per attestation increase from 1 to 2?
These are not trivial changes. They affect every company using the protocol. They require broad consensus, not a unilateral decision. And the stakeholders who vote on them — DPO2U partners, auditor agents, and token holders — have a legitimate interest in keeping their positions private.
A governance system for DPO2U must satisfy four requirements simultaneously:
| Requirement | Description | Why it fails on standard chains |
|---|---|---|
| Authorization | Only verified stakeholders can vote | Easy to implement, but voter identity is public |
| Integrity | Process is tamper-proof; outcome is correctly executed | Achievable, but requires trusted execution |
| Privacy | A stakeholder's vote direction and weight are secret | Impossible on transparent blockchains |
| Expressiveness | The system captures the intensity of preference, not just its direction | One-token-one-vote is binary and plutocratic |
Quadratic voting — the mechanism
Quadratic Voting was proposed by economist E. Glen Weyl as a solution to the fundamental problem of preference aggregation in collective decisions. The core insight is elegant: allow participants to buy multiple votes, but make the cost grow quadratically.
| Votes cast | Cost in voice credits | Marginal cost of last vote |
|---|---|---|
| 1 | 1 | 1 |
| 2 | 4 | 3 |
| 3 | 9 | 5 |
| 5 | 25 | 9 |
| 10 | 100 | 19 |
The quadratic cost curve has a critical property: it becomes prohibitively expensive to dominate any single vote, but it allows a passionate minority to out-vote an indifferent majority. A stakeholder who cares deeply about a proposal can cast 5 votes (costing 25 credits), while 25 indifferent stakeholders each casting 1 vote (costing 1 credit each) will collectively match them — but only if all 25 actually show up and vote.
Why QV + ZK is the right combination
Quadratic Voting has a known vulnerability on public systems: if votes are visible, participants can coordinate to split their votes strategically, undermining the mechanism. Zero-knowledge proofs eliminate this attack vector. When the number of votes cast is a private witness in a ZK circuit, no coordination is possible because no one knows how others are voting until the final tally is revealed.
The system architecture
The governance system consists of two interconnected contracts:
The key design principle is separation of concerns: the ComplianceRegistry holds the protocol state and enforces that only the governance contract can change it. The ProtocolGovernance contract holds the democratic process. Neither contract needs to trust the other's internal logic — the ZK proofs guarantee correctness.
The smart contracts
ComplianceRegistry.compact
The registry exposes a single governance hook: updateMinScore. The assert check means this function can only be called by the governance contract, making it impossible for any individual to change the standard unilaterally.
// ComplianceRegistry.compact
pragma language_version >= 0.2.0;
export ledger ComplianceRegistry {
owner: Address;
minComplianceScore: nat;
schemaVersion: string;
}
// Only callable by the governance contract (the owner)
export circuit updateMinScore(newScore: nat) {
const registry = ComplianceRegistry.get(self());
assert(sender() == registry.owner,
"Only the governance contract can update security standards");
assert(newScore >= 50n && newScore <= 100n,
"Score must be between 50 and 100");
disclose(() => {
registry.minComplianceScore = newScore;
});
}
ProtocolGovernance.compact
This is the core of the system. The castVote circuit is where the QV logic and the ZK privacy guarantee intersect. The voter's private key, the number of votes, and the vote direction are all private witnesses — they are inputs to the ZK circuit but are never recorded on the ledger.
// ProtocolGovernance.compact
pragma language_version >= 0.2.0;
ledger Proposal {
id: Field;
proposer: PubKey;
targetContract: Address;
newMinScore: nat;
votesFor: nat;
votesAgainst: nat;
executed: bool;
deadline: Timestamp;
nullifiers: Set<Field>;
}
export ledger Governance {
registryAddress: Address;
eligibleVoters: Map<PubKey, nat>;
proposals: Map<Field, Proposal>;
totalVoters: nat;
}
// Private inputs: voterPrivateKey, numVotes, inFavor
// Public inputs: proposalId
export circuit castVote(
proposalId: Field,
voterPrivateKey: PrivKey, // PRIVATE — never leaves the client
numVotes: nat, // PRIVATE — how many votes to cast
inFavor: bool // PRIVATE — direction of the vote
) {
const gov = Governance.get(self());
const proposal = gov.proposals.get(proposalId);
const voterPublicKey = getPubKey(voterPrivateKey);
// Voter eligibility
assert(gov.eligibleVoters.has(voterPublicKey),
"Not an eligible stakeholder");
// Voting period is open
assert(now() < proposal.deadline,
"Voting period has ended");
// Quadratic cost: n votes costs n² credits
assert(numVotes >= 1n, "Must cast at least 1 vote");
const cost = numVotes * numVotes;
// Sufficient voice credits
const currentBalance = gov.eligibleVoters.get(voterPublicKey);
assert(currentBalance >= cost,
"Insufficient voice credits for this number of votes");
// Nullifier prevents double-voting
const nullifier = hash(proposalId, voterPrivateKey);
assert(!proposal.nullifiers.has(nullifier),
"Voter has already voted on this proposal");
// State transition: only effects are public
disclose(() => {
gov.eligibleVoters.set(voterPublicKey, currentBalance - cost);
if (inFavor) {
proposal.votesFor += numVotes;
} else {
proposal.votesAgainst += numVotes;
}
proposal.nullifiers.add(nullifier);
});
}
// Can be called by anyone after the deadline
export circuit executeProposal(proposalId: Field) {
const gov = Governance.get(self());
const proposal = gov.proposals.get(proposalId);
assert(now() >= proposal.deadline,
"Voting period has not yet ended");
assert(!proposal.executed,
"Proposal has already been executed");
// QV tally: compare square roots
const sqrtFor = isqrt(proposal.votesFor);
const sqrtAgainst = isqrt(proposal.votesAgainst);
assert(sqrtFor > sqrtAgainst,
"Proposal did not pass");
disclose(() => {
proposal.executed = true;
ComplianceRegistry
.from(gov.registryAddress)
.updateMinScore(proposal.newMinScore);
});
}
The client-side scripts
Casting a quadratic vote
The voter decides how many voice credits to spend. The script calculates the cost and generates the ZK proof locally. The private key, vote count, and direction never leave the client.
import { MidnightProvider, Wallet, Proof } from "@midnight-ntwrk/sdk";
const provider = new MidnightProvider({ network: "preprod" });
const voterWallet = Wallet.fromPrivateKey(VOTER_PRIVATE_KEY, provider);
async function castQuadraticVote(
proposalId: bigint,
numVotes: bigint,
inFavor: boolean
) {
const cost = numVotes * numVotes;
console.log(`Casting ${numVotes} vote(s) — cost: ${cost} voice credits.`);
// Generate the ZK proof locally.
// numVotes, inFavor, and voterPrivateKey are private witnesses.
const proof = await Proof.prove(
"ProtocolGovernance.compact",
"castVote",
{
proposalId,
voterPrivateKey: voterWallet.privateKey, // stays local
numVotes, // stays local
inFavor, // stays local
}
);
// Submit the proof. The network verifies the math,
// not the inputs. It never sees who voted, how many, or which way.
const tx = await provider.sendProof(proof);
await tx.wait();
console.log(`Vote cast. Transaction: ${tx.hash}`);
console.log(`Your identity, vote count, and direction remain secret.`);
}
// Example: Cast 4 votes in favor (cost: 16 credits)
castQuadraticVote(PROPOSAL_ID, 4n, true);
Executing a passed proposal
After the voting deadline, any participant can trigger execution. The contract verifies the result and, if the proposal passed, calls ComplianceRegistry.updateMinScore() automatically.
async function executeProposal(proposalId: bigint) {
const proof = await Proof.prove(
"ProtocolGovernance.compact",
"executeProposal",
{ proposalId }
);
const tx = await provider.sendProof(proof);
await tx.wait();
console.log("Proposal executed. ComplianceRegistry standard updated.");
}
Why this model is superior
By combining Quadratic Voting with Zero-Knowledge Proofs, you create a governance system with properties that are impossible to achieve separately.
Expressiveness without plutocracy. A single large stakeholder can cast 10 votes on a critical proposal, but it costs them 100 voice credits. Twenty smaller stakeholders casting 2 votes each (costing 4 credits each, 80 total) can collectively match them. The mechanism rewards breadth of conviction, not just depth of pockets.
Privacy prevents strategic manipulation. The most dangerous attack on QV in public systems is strategic coordination: if you can see how others are voting, you can time your votes to maximize impact. With ZK proofs, the vote count and direction are private witnesses. The tally updates on-chain, but no one knows who contributed what.
Security through cryptography, not trust. The rules of governance are encoded in ZK circuits. The assert() statements are mathematical constraints, not policies that can be overridden. The governance contract cannot be bribed, coerced, or hacked into accepting an invalid vote — the cryptography guarantees it.
Progressive decentralization. DPO2U can launch with a small set of eligibleVoters (initial partners and auditors) and expand the governance set over time. Each expansion is itself a governance proposal, voted on by the existing set. The protocol grows its own democratic legitimacy.
Conclusion
We have built a governance system that achieves something previously impossible: a mechanism that is simultaneously private, expressive, and cryptographically secure. The combination of Quadratic Voting and zk-SNARKs on Midnight is not just a better voting system — it is a new primitive for building protocols that can evolve without centralization.
For DPO2U, this is the foundation for a living compliance standard. The minComplianceScore, the active schema version, the required number of auditor signatures — all of these can be updated through a process that is transparent in its outcomes but private in its deliberations. That is exactly what a privacy-first protocol requires.
References:
- Weyl, E. G. (2017). "Quadratic Voting." The Handbook of Market Design. Oxford University Press.
- Buterin, V., Hitzig, Z., & Weyl, E. G. (2018). "Liberal Radicalism: A Flexible Design for Philanthropic Matching Funds."
- Midnight Network. "Zero-knowledge demystified."
- DPO2U. "Smart Contracts." | "Compact Is Not Solidity."
