Skip to content

Feat/protocol voting - #2

Merged
USCMig merged 3 commits into
mainfrom
feat/protocol-voting
Aug 2, 2026
Merged

Feat/protocol voting#2
USCMig merged 3 commits into
mainfrom
feat/protocol-voting

Conversation

@USCMig

@USCMig USCMig commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Enabling ZEC protocol voting

USCMig and others added 3 commits August 2, 2026 01:28
…tion

First piece of protocol voting (coinholder polling). Pure, testable core with no
wallet/network dependency:

- poll_hash(bytes): hex SHA-256 of the ballot definition as published (hashes raw
  bytes, never a re-serialized copy, so it matches the publisher's hash).
- VoteEntry { Abstain | Choice(idx) | FreeForm(s) } serializing to the exact v1
  wire shapes (null / number / string).
- validate_votes: enforces one answer per question, in-range indices, and
  free-form only where allowed (a malformed memo counts as all-abstain on-chain).
- encode_vote_memo: builds the v1 memo JSON and refuses over the 512-byte limit.
- BallotQuestion: lenient parse to drive the UI and derive QuestionShape, kept
  independent of the still-finalizing ballot schema.

Casting will reuse the existing shielded-send + FROST path. 7 unit tests pass.
Open decision: v1 weights by a TRANSPARENT-balance snapshot; a shielded FROST
treasury needs the shielded-weighting poll variant to carry weight.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
prepare_vote validates the votes, computes the ballot poll-hash, encodes the
Vote Cast Memo v1, and builds a shielded payment carrying that memo to the poll's
reception Z-address — returning the same DraftTransaction a normal send does, so
the existing FROST signing + broadcast path casts the vote unchanged.

Per the NU7/Zodl shielded-polling design (ValarDragon et al.), vote WEIGHT is
derived by the poll's tally from a shielded-balance snapshot at a cutoff height,
not from the amount delivered with the vote — so amount_zatoshis is the minimal
the poll requires (dust), and the group just holds its balance through the
snapshot window. Validation runs before anything is built, since a malformed memo
is counted as all-abstain on-chain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completes the coinholder-poll voting feature with manual ballot entry:

- BallotDefinition parse + question_shapes in the voting core (lenient, unknown
  fields ignored); 9 voting unit tests pass.
- wallet_prepare_vote command: parses the pasted ballot for validation shapes,
  maps UI VoteInput -> VoteEntry, and returns a DraftTransaction whose memo is the
  encoded vote. Casting reuses wallet_send unchanged (recipient = poll reception
  address, memo = the vote), so no new signing/broadcast plumbing.
- New "Vote" tab: paste ballot JSON + reception address, answer each question
  (fixed choice / free-form where allowed / abstain), then review + sign via the
  existing FROST ceremony. Review panel labels it "Cast vote" and explains weight
  comes from the poll's snapshot, not the dust amount sent.
- TODO: automated/programmatic poll source (fetch + render + results) as a
  follow-up; the casting core is already source-agnostic.

cargo test (voting) + full backend build + tsc + vite build all green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@USCMig
USCMig merged commit 2772fd0 into main Aug 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant