feat: add ConfidentialVoting amendment (encrypted-tally ballots)#36
Open
dangell7 wants to merge 1 commit into
Open
feat: add ConfidentialVoting amendment (encrypted-tally ballots)#36dangell7 wants to merge 1 commit into
dangell7 wants to merge 1 commit into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ConfidentialVoting
Adds a native ballot primitive with an encrypted, homomorphic tally. Individual choices and the running count stay hidden while a ballot is open; results are revealed at close with a decryption-correctness proof, so the tally authority can see results early but cannot lie about them.
This reuses the confidential-MPT crypto stack (
mpt-crypto, EC-ElGamal + bulletproofs + compact sigma proofs) that already ships in develop — no new dependency, no Rust changes.What it does
A
Ballotledger object holds one ElGamal ciphertext per option under a tally key.BallotCastVotesubmits a vector of encrypted votes (chosen option gets the weight, the rest get zero — every counter updates each cast, so which one changed leaks nothing) and the transactor homomorphically adds them into the tally. At close,BallotFinalizepublishes the plaintext counts with a per-option decryption proof that validators check against the on-ledger ciphertexts.Two eligibility modes, one per ballot:
Each cast is verified for real: an aggregated bulletproof proves every option value is non-negative (no subtracting weight from an option you dislike), a sum check pins the vector to the voter's weight (no inflation), and a per-option compact-sigma proof links each ciphertext to its range-proven commitment so the tally can't be corrupted. Double-voting is blocked by a one-
BallotVote-per-account rule (plus the token-mode lock).New objects and transactions
Ballot,BallotVoteBallotCreate,BallotCastVote,BallotFinalize,BallotDeleteConfidentialVotingA note on the crypto
Confidential transfer never needs to prove a value encrypted under a key you don't own is well-formed — the sender always owns their balance key. A vote encrypts under the tally key, which the voter does not own, so it needs verifiable encryption. The
mpt-cryptolow-level API already exposes what that takes:secp256k1_compact_standard_verifygives the ciphertext-to-commitment linkage (its balance-linkage terms are neutralized with a canonical witness that constrains nothing about the vote), andsecp256k1_elgamal_verify_encryptiongives the sum-to-weight check. So both modes are sound today with the merged library.Trust model (v1)
The tally authority holds the decryption key — it can see individual votes, same trust as a transfer agent or registrar. It cannot lie about the result. This is confidential to the market, transparent to the registrar; it is not a secret ballot against the authority. A threshold-key committee (secret against the authority too) and recast-for-coercion-resistance are future work — see the spec.
Tests
New suite
ConfidentialVoting(6 cases, 274 tests): amendment gating, token-mode and credential-mode happy paths end to end (create → cast → finalize with real proofs, tally decrypts to the exact counts), double-vote / window / eligibility / malformed-proof rejection, lock enforcement, and a low-level round-trip check of the linkage primitive incl. a tamper case. All proofs are generated live against the real library — no fixtures. Related suites (ConfidentialTransfer, MPToken, Invariants) still pass.Known follow-ups (not blocking)
lsfVoterRecoverable) accept a voter key + mirror vector and the linkage proof already pins that mirror; a dedicated Schnorr PoK on the voter key is not yet enforced.sum(results)provably equals total cast weight; an explicit invariant restating that bound is optional hardening.