Private Tari Governance Voting Tool

Private Tari Governance Voting Tool Progress Update

With the recent discussion around CC voting and governance, I thought this was a good time to share what I have been building.

I have been developing an open source private voting tool for Tari governance. The goal is to allow an eligible person to vote without revealing which voter they are, while still allowing the election and final results to be independently verified.

What is already working and tested

  • Real Tari Triptych linkable ring proofs for anonymous voter eligibility

  • Election scoped nullifiers that prevent the same voter from voting twice without revealing their identity

  • Proofs cryptographically bound to the specific election, voter registry, and ballot selection

  • Real multi voter elections and larger voter registries

  • Duplicate voting attempts, including generating a completely new proof and ballot from the same voter

  • Replay attacks

  • Altered ballot selections

  • Corrupted and invalid proofs

  • Wrong election ballots

  • Altered voter registries

  • Governance source revision binding

  • Strict canonical ballot encoding and decoding

  • Election lifecycle enforcement

  • Deterministic tallies

  • Local voter ballot generation, verification, and export

  • Organizer ballot import and independent verification

  • A separate verifier that can verify an election without trusting the GUI or election operator

The current protocol and registry limits support up to 4,096 eligible voters in a single election. That is the configured maximum, not a claim that I have already run a full 4,096 voter election through the GUI. Larger scale and performance testing is still part of the testing before release.

A large amount of development time has gone into trying to break the system rather than simply making a Vote button work.

The ballot office now accepts the same canonical ballot bytes regardless of how they are delivered. That means an offline ballot file and the upcoming private Internet submission system use the exact same cryptographic verification and duplicate protection.

What is being built next

The next major part is private Internet ballot submission.

The transport design currently includes:

  • Ed25519 signatures to authenticate the official election transport configuration

  • RFC 9180 HPKE encryption

  • X25519

  • HKDF SHA256

  • ChaCha20 Poly1305

  • One click private submission through Tor and an onion service

  • An optional non Tor private relay using a split trust design

  • Fixed size padded messages to reduce simple ballot size correlation

  • Privacy preserving batching to reduce timing correlation

  • Private acceptance and inclusion receipts

  • Batch commitments with operator side Tari Ootle anchoring

  • No silent fallback to an identifiable connection if private submission fails

Voters will not need a Tari wallet, tTARI, an account, or their own Ootle transaction.

Ballot privacy

The current V1 is an anonymous ballot system, rather than a cryptographically sealed ballot system.

The ballot can ultimately be publicly verified, but the cryptography is designed so the ballot is not tied to the identity of the voter.

Fully sealed ballots, where even the election operator cannot inspect ballot choices before voting closes, could be added later if Tari actually needs that level of protection.

I would rather release and test the anonymous V1 first, see what the community actually wants, and then decide whether the additional complexity of sealed ballots is worthwhile.

Open source release

The project will be released open source on GitHub soon for public testing and review.

The goal is for anyone to be able to:

  • Inspect the code

  • Reproduce the tests

  • Independently verify elections and final tallies

  • Review the privacy and threat model

  • Try to break it

  • Suggest improvements

I should also have the first proper GUI screenshots to share within the next week, including the voter flow, anonymous proof generation, ballot verification, and organizer side. I’ll hopefully deploy it all within this week :crossed_fingers:

There is still more security testing, larger scale testing, and private transport work to finish before the first public release, but the project is well beyond the initial proof of concept stage now.

@Fox @kinkajou @naveen @Okz @0xhra

9 Likes

Mandatory tool … thanks @GSXRspartan ! happy to contribute with testing :))

Please share GitHub link one you think it is ready, happy to join forces and help testing.

Absolutely @0xhra & @Okz :+1: I appreciatethe support. I’ll share the GitHub repo once I have the Governance Pilot at a point where outside testing will actually be useful.

It’s getting pretty close now. I’m mostly down to polishing the GUI and doing the full two computer end to end testing so I know the real world setup works cleanly before handing it to everyone.

I’d really appreciate both of you trying to break it once it’s up. Duplicate voting, replay attempts, altered ballots, bad proofs, corrupted election archives, privacy or linkability issues, independent tally verification, anything you can think of.

The more hostile the testing is before anyone considers using it for something important, the better :laughing:

2 Likes

GUI is looking polished​:ok_hand: the two and three computer tests are tomorrow (or the next day at the latest). If everything passes, I think it’s time for Github. Multiple computers normally find some bugs, but I have hope.

I’ll post a screenshot of the mock election soon :turtle: :crossed_fingers:

I’m almost done with the hardening and persistence work I wanted finished before multi computer testing. It took a little longer than I expected to get here, but two+ computer testing should finally happen tomorrow.

The Guide / How It Works section explains a lot of what is going on under the hood, and this image will live at the top of it. Figured it would make a good first teaser before I post screenshots from the multi-computer tests tomorrow.

I still need to test the Ootle anchor path, but that’s intentionally pretty boring. It’s just the public commitment/anchor being written to Ootle. The private ballot data stays off chain.

2 Likes

Hey !

Great work, just a question. From the diagram, it looks like every time a new election happens the election package needs to be imported. If the public key of the Voter remains the same. I think for council/CC voting the Organizer/Ballot Office task could be automated?

You are quick on the draw :laughing:

That’s basically right. The election package is intentionally unique to each election, even if a voter keeps the same public key. It binds the election ID, ballot choices, eligibility snapshot and proof scope, which is also why proofs/nullifiers can’t just be reused across elections.

I agree the Organizer/Ballot Office side could be automated quite a bit for recurring Council/CC votes. A new election package could be generated and published automatically, then voters could discover, fetch and verify it instead of manually importing a file every time.

I’d still keep the verification boundary though. Automate the boring transport, not the cryptographic checkpoint. :laughing:

1 Like

Sorry if i am asking too many questions, just very curious as i built something similar several years ago. Are voter’s public key supposed to be public? If voter’s public key are public can the election package be verified to not include any other public-key. Even if the voter’s public key are not public is their a way to use zk-proof to make this happen?

1 Like

No worries, these are good questions. Keep them coming. The voter’s public enrollment key is meant to be public, while the corresponding private credential stays only with the voter.

The election package contains the eligible public key registry, and that registry is cryptographically committed into the election manifest. So anyone can verify that they are looking at the exact registry the election was created and frozen with. If an organizer changed, added, or removed a key afterward, the election commitment would change and the ballot proofs would no longer be for the same election.

The anonymity part comes from the Triptych style proof. A voter proves that they control one of the private keys corresponding to the public keys in that registry, without revealing which public key is theirs. The election bound nullifier then prevents that same credential from voting twice without identifying the voter.

One important distinction though: cryptography can prove that the registry hasn’t changed after it was committed, but it can’t magically know whether an organizer put an illegitimate key into the original eligibility list. That’s why the registry/source itself also needs to be auditable.

You could hide the public key list behind commitments and use ZK membership proofs instead, but that adds quite a bit of complexity. For this design, keeping the eligibility keys public while hiding which one signed the ballot gives us the auditability we want without exposing voter to ballot linkage.

1 Like

There’s also a second privacy layer I didn’t mention above. Ballots aren’t intended to be immediately exposed one by one as they arrive. The design uses batching, shuffling, and delayed publication to make timing correlation harder. Triptych hides which eligible key produced the ballot; batching helps hide when a particular voter’s submission became part of the public record. They solve two different leakage problems.

1 Like

Governance Pilot update :turtle::locked_with_key::ballot_box_with_ballot:

This update took a little longer than I expected because I decided to wire Tor directly into the voting flow before posting the screenshots.

That turned into a bit more work than add Tor :sweat_smile: The first real end to end Tor submission passed tonight! The screenshots below walk through the current flow from creating and freezing an election, proving anonymous eligibility and changing a choice locally, through private Tor delivery and ballot office acceptance.

Tari Private Ballot is an independent community-built governance pilot focused on anonymous eligibility, one vote enforcement, private ballot delivery, and public verification.

Elections are created step by step with the ballot type (candidate election, Governance proposal, or Ballot measure), governance source, eligible voter set, options, rules, and review all defined before the election is frozen.


Freezing locks the election definition and produces the canonical files used by voters and verifiers. The manifest, voter registry, and option set are all commitment bound before voting opens.


Eligibility is Triptych-proven privately: the frozen registry contains voters’ public enrollment keys, while the ballot proves control of one eligible credential without revealing which public key is the voters. An election bound nullifier enforces one vote per credential.


The voter can choose a response locally without submitting anything. At this point the choice can still be changed. Abstaining is allowed if selected during the original ballot creation.


The choice can be changed before the anonymous ballot is finalized. In this test I switched to the clearly superior answer. :grin: Once the ballot is saved for offline delivery or submitted over Tor, that choice is locked for that election and cannot be changed on that device.

While voting is open, participation can remain hidden. Tallying stays locked until the organizer deliberately closes the election, so nobody can peek at how the vote is trending before it ends.

Ballots can be saved for offline/manual delivery or submitted privately over Tor. Here the private connection is ready, and the encrypted ballot can be sent directly to the ballot office.

The other side of the connection: the ballot office received the Tor submission and the accepted ballot count moved from 0 → 1.

End to end Tor submission succeeded! The voter only gets a success state after an authenticated organizer receipt is verified, and the ballot is then locked for that election.

Tor is only the private delivery layer. The encrypted ballot package itself can be delivered through essentially any method, including email, Discord, Telegram, removable media, or a particularly dependable pigeon. :bird: Tor is the built-in option for voters who also want to hide the network metadata associated with delivering their ballot. The election itself is designed to remain independently verifiable: the finalized archive contains the canonical election data, accepted ballots, commitments, and proof material needed for anyone to independently verify the result without having to trust the ballot office.

Triptych style linkable ring proofs were a deliberate choice for this design and for Tari. The frozen voter registry contains the public enrollment keys, while Triptych lets a voter privately prove control of one eligible credential without revealing which public key belongs to them. An election bound nullifier enforces one vote per credential. The Ootle compatibility was one of the reasons I chose this proof approach for the pilot.

That structure also fits naturally with how I wanted to use Tari Ootle. Individual voters do not need to create identifiable on chain voting transactions. The voting and proofs can remain privacy preserving off chain, while the finalized election/archive commitment can optionally be anchored publicly on Ootle as an additional integrity check. If there is a testnet reset nothing is lost because of the local copies.

So, in short:

Tor protects the voter’s network path → Triptych privately proves eligibility → election-bound nullifiers enforce one vote → the finalized archive lets anyone independently verify the election → Tari Ootle can optionally anchor the aggregate record without putting individual votes on chain.

What’s next:

I’ll be completing multi computer testing ASAP, including separate voter and ballot office machines, restart/recovery, duplicate vote attempts, final archive verification, and finishing the Ootle anchor.

Once that testing passes, the project will be released open source on GitHub, with builds planned for Windows, Linux, and macOS.

5 Likes

Great work. Now stop teasing us and let us try it.

1 Like

:joy: Fair. I’m making Tor one click for both the voter and ballot office right now and testing it. PowerShell commands and digging through file locations definitely aren’t friendly to the average person.

Then it’s one last round of abuse testing across multiple computers and finishing the Ootle anchor. After that I’ll stop teasing and you guys can try to break it yourselves. I’m starting to run out of excuses anyway. :grin: I want to ship it.

Quick update on the private voting tool :eyes: The guide and UI has changed a lot. It is more friendly to the not so technical user. One click Tor on ballot office and voter and guided voting and creating has been added.

I’m getting close. The core build is now far enough along that most of the work has shifted from adding features to trying to break what I already built.

Right now I’m running 0x Alpha (thanks @0xhra ) audits across the codebase and doing multi computer testing to make sure the full voter and ballot office flow holds up outside a single machine dev environment.

That includes testing things like malformed ballots, replay attempts, duplicate voting, verifier consistency, archive tampering, networking/Tor behavior, and the general collection of terrible things computers enjoy doing the moment you call something “finished.” :laughing:

Once this testing and audit round is clean, I will be very close to the point where other people can start trying to break it too.

5 Likes

It’s almost time…hope you are ready to vote anonymously!

The load tester app will ship with it which will allow up to the max 4096 voters to be tested over Tor using Tari Triptych ring signatures. I hope you all are excited as I am to drop it.

2 Likes