Skip to content

valargroup/vote-sdk

Repository files navigation

Shielded Vote Chain

License: MIT

A Cosmos SDK application chain for private on-chain voting using Zcash-derived cryptography (Halo2 ZKP circuits, RedPallas signatures, ElGamal encryption, Poseidon Merkle trees).

Specification: Shielded Voting Protocol ZIP

Quick Start

Prerequisites

  • mise — task runner + toolchain installer (recommended)
  • Alternatively, install Go 1.24+ and Rust stable directly

Run a local chain with mise

# 1. Install the toolchain listed in mise.toml (Go, Rust, Node, buf, jq).
mise install

# 2. Build + install svoted and create-val-tx into $GOBIN (on PATH).
mise run install

# 3. Create .env with the vote-manager private key(s) (one-time).
cp .env.example .env
# Edit .env and set VM_PRIVKEYS (comma-separated 64-char hex keys; generate
# each with: openssl rand -hex 32). For a single-vote-manager chain, provide
# exactly one key. The coordinator threshold defaults to 1 unless configured
# otherwise.

# 4. Wipe + init a single-validator devnet, then start the daemon.
mise run chain:init
mise run chain:start

Other useful tasks: mise run chain:init-multi + mise run chain:start-multi for a 3-validator local chain, mise run test:go for the Go test suite, mise tasks to list everything.

Contributing from a clone (local binaries, join script with SVOTE_LOCAL_BINARIES, task reference): see CONTRIBUTING.md.

Without mise (direct make)

# Build and install
make circuits      # Rust ZKP circuit static library
make build-ffi     # svoted with ZKP + signature verification
# (or) make build  # no FFI — stubs Halo2/RedPallas verification; dev only

# Init + start (same .env step as above applies)
make init
make start

Consensus timing defaults

svoted overrides CometBFT defaults at startup to reduce block time (~1.2s with 30 validators vs ~4-6s with defaults). See docs/blocktimes.md for the full parameter table, Osmosis comparison, and benchmark results confirming safety.

Test

# Go unit tests (no Rust dependency)
make test

# Halo2 ZKP verification tests (requires: make circuits)
make test-halo2

# All FFI-backed tests (Halo2 + RedPallas)
make test-all-ffi

# Rust circuit unit tests
make circuits-test

Project Structure

app/            Cosmos app, ABCI handlers, PrepareProposal/ProcessProposal
api/            REST API handlers, codec, tx wrapper
circuits/       Rust crate: Halo2 ZKP + RedPallas FFI (staticlib for CGo)
cmd/svoted/     Chain binary and CLI commands
crypto/         Cryptographic primitives (ECIES, ElGamal, Shamir, ZKP, etc.)
deploy/         Caddyfile for HTTPS reverse proxy
docs/           Deployment and protocol documentation
internal/       Helper server (share submission, SQLite, random delays)
proto/          Protobuf definitions (buf-managed)
scripts/        Chain init scripts, validator tooling
x/vote/         Vote module: ceremony, voting rounds, tally, keeper

License

This project is licensed under the MIT License.


Protocol Documentation

Technical Assumptions

  1. The chain launches with a single genesis validator. Additional validators join post-genesis via MsgCreateValidatorWithPallasKey, which atomically creates the validator and registers their Pallas key for the ceremony. Raw MsgCreateValidator is blocked in the ante handler for live transactions. Validator set changes beyond that are handled via major upgrades or a PoA module (future).
  2. Client interaction avoids Cosmos SDK protobuf encoding:
    • Tx submission: Client sends a plain JSON POST; server handler parses JSON and encodes as needed.
    • Query: gRPC gateway supports JSON out-of-the-box.
  3. No native x/gov module. The vote module implements custom private voting instead of reusing standard Cosmos governance.

Architecture

Module: x/vote

The vote module has two major subsystems: the EA Key Ceremony (automatic per voting round) and Voting Rounds (transition from PENDING through ceremony to ACTIVE).

EA Key Ceremony (Per-Round)

The EA key ceremony runs automatically per voting round. Each MsgCreateVotingSession creates a round in PENDING status and snapshots all eligible validators (bonded + registered Pallas key) into the round's ceremony fields. The ceremony proceeds automatically via PrepareProposal — no manual intervention is needed after initial Pallas key registration.

Per-Round Ceremony State Machine

Ceremony state is stored on the VoteRound itself (fields ceremony_status, ceremony_validators, etc.). There is no global singleton ceremony state.

  PENDING (REGISTERING) ──> PENDING (DEALT) ──> ACTIVE (CONFIRMED)
             │                    │                (all acked)
             │ timeout            │ timeout (≥ 1/2)
             v                    v
  CEREMONY_FAILED     ACTIVE (CONFIRMED)
                         + strip non-ackers
                                 │ timeout (< required quorum)
                                  v
                          CEREMONY_FAILED
From To Trigger Condition
REGISTERING DEALT Auto-deal via PrepareProposal Block proposer is a ceremony validator
DEALT CONFIRMED + ACTIVE MsgAckExecutiveAuthorityKey All validators acked (fast path)
DEALT CONFIRMED + ACTIVE EndBlocker timeout >= required(n) = ceil(4n/5) acks at timeout; non-ackers stripped
REGISTERING CEREMONY_FAILED EndBlocker timeout DKG contributions incomplete at timeout; non-contributors jailed
DEALT CEREMONY_FAILED EndBlocker timeout Below required(n) = ceil(4n/5) acks

Key behaviors:

  • Fast path vs timeout — the fast path confirms when ALL validators ack (no stripping needed). The timeout path confirms with required(n) = ceil(4n/5) acks and strips non-ackers.
  • Auto-deal — the block proposer automatically deals when it detects a PENDING round in REGISTERING state. No manual ceremony.sh deal step.
  • Auto-ack — each block proposer auto-acks via PrepareProposal when it detects a DEALT round.
  • Ceremony timeout jailing — validators who miss REGISTERING contributions are jailed through x/slashing until the chain's downtime jail duration elapses. DEALT non-ackers are stripped from successful timeout confirmations but are not jailed because a missing ack is not reliable blame evidence.
  • Ceremony log — each state transition appends a timestamped entry to ceremony_log on the round, visible in queries and the admin UI.

Pallas Key Registration and Rotation

Validators register their Pallas key via MsgRegisterPallasKey or MsgCreateValidatorWithPallasKey. Keys are stored in a global registry (prefix 0x0C) and persist across rounds.

A registered key can be replaced via MsgRotatePallasKey. Rotation is rejected while the validator is participating in any PENDING round ceremony (the snapshotted key would become stale and ECIES payloads would be undecryptable). The old key's reverse-lookup index is cleaned up so it can be reused.

Auto-Deal and Auto-Ack via PrepareProposal

PrepareProposal composes two ceremony injectors:

  1. Auto-deal — if a PENDING round is in REGISTERING state and the proposer is a ceremony validator, generate DKG material, Shamir-split it into (t, n) shares with threshold = max(2, ceil(2n/3)) for n >= 2, ECIES-encrypt share_i to each validator, publish Feldman commitments, and inject MsgContributeDKG.
  2. Auto-ack — if a PENDING round is in DEALT state and the proposer hasn't acked, decrypt the payload to recover their share, verify share_i * G == VK_i (threshold mode) or ea_sk * G == ea_pk (legacy), inject MsgAckExecutiveAuthorityKey, and write the share/key to disk.

Timeout (EndBlocker)

REGISTERING and DEALT phases have timeouts (default: 10 minutes):

  • REGISTERING timeout: Jail validators that did not contribute DKG material and mark the pending round CEREMONY_FAILED.
  • DEALT timeout, >= required quorum: Strip non-ackers, confirm ceremony, activate round.
  • DEALT timeout, < required quorum: Mark the pending round CEREMONY_FAILED without jailing non-ackers, because missing acks are not reliable blame evidence. A later create transaction can retry the same vote metadata because vote_round_id includes the round creation height.

ECIES Encryption Scheme

Each validator's ea_sk share is encrypted using ECIES over the Pallas curve with SpendAuthG as the generator:

  1. E = e * SpendAuthG (ephemeral public key)
  2. S = e * pk_i (ECDH shared secret)
  3. k = SHA256(E_compressed || S.x) (symmetric key)
  4. ct = ChaCha20-Poly1305(k, nonce=0, ea_sk) (authenticated encryption)

Vote Coordinator Actions

The vote coordinator policy is an on-chain N-of-M approval system for privileged chain operations. It stores coordinator account addresses plus a threshold. Coordinator-owned actions are proposed on-chain, approved by current coordinators, and execute automatically once the current threshold is met.

Coordinator approval gates voting session creation, coordinator membership and threshold changes, software upgrades, endorser mapping changes, and coordinator-funded sends. Validator-owned actions such as Pallas key registration, validator creation, ceremony participation, staking edits, unjail, and mapped endorser actions remain outside this multisig.

For the plain-English operator workflow, see docs/vote-coordinator-actions.md.

Voting Rounds

After the ceremony reaches CONFIRMED and the coordinator policy is configured, voting sessions can be created through coordinator action approval.

ACTIVE ──> TALLYING ──> FINALIZED
  ^
  │ (gated: requires CONFIRMED ceremony + coordinator threshold approval)

MsgCreateVotingSession reads ea_pk from the confirmed ceremony state (not from the message). The round stores its own copy of ea_pk for future key rotation support. It is executed as a coordinator action after enough current coordinators approve it. An optional description field provides human-readable context for the round.

MsgSubmitPartialDecryption is auto-injected via PrepareProposal when a round is in TALLYING state and threshold mode is active. Each validator submits D_i = share_i * C1 per accumulator. Cannot be submitted through the mempool.

MsgSubmitTally is auto-injected via PrepareProposal once t partial decryptions exist on-chain. The proposer Lagrange-combines them to recover ea_sk * C1, runs BSGS, and submits plaintext totals. Cannot be submitted through the mempool.

PrepareProposal / ProcessProposal Pipeline

PrepareProposal composes four injectors that run sequentially on each proposed block:

  1. Ceremony DKG contribution injection — if a PENDING round is in REGISTERING and the proposer is a ceremony validator, auto-contribute via MsgContributeDKG
  2. Ceremony ack injection — if a PENDING round is in DEALT and the proposer hasn't acked, auto-ack via MsgAckExecutiveAuthorityKey
  3. Partial decryption injection (threshold mode) — if a TALLYING round has threshold > 0 and the proposer hasn't yet submitted, compute D_i = share_i * C1 per accumulator and inject MsgSubmitPartialDecryption
  4. Tally injection — when t partials are on-chain (threshold mode) or ea_sk is on disk (legacy), Lagrange-combine and BSGS-solve, then inject MsgSubmitTally

ProcessProposal validates all injected txs on non-proposer validators before accepting a block. MsgAckExecutiveAuthorityKey, MsgSubmitPartialDecryption, and MsgSubmitTally are all blocked from the mempool (CheckTx rejects them).

Custom Wire Format

Rationale

The standard Cosmos SDK Tx envelope requires a signer address, fee fields, and a conventional signature (secp256k1 or ed25519). Vote-round messages (MsgDelegateVote, MsgCastVote, MsgRevealShare) cannot use this envelope because they are authenticated via ZKP + RedPallas spend-auth signatures — there is no conventional Cosmos account involved. Similarly, MsgContributeDKG, MsgAckExecutiveAuthorityKey, and MsgSubmitPartialDecryption are auto-injected by the block proposer via PrepareProposal and are never client-signed at all.

The custom wire format is the minimal encoding that satisfies both cases: a single-byte type tag lets the TxDecoder unambiguously identify the message type without parsing a full TxBody, and the tag byte acts as the sole discriminator between the custom path and the standard Cosmos SDK path. Messages that do have a conventional signer, including coordinator action proposals/approvals and validator setup messages, use the standard Tx envelope and flow through normal signature verification.

Wire Format

Each custom transaction is a 1-byte tag followed by a protobuf-encoded message body:

[tag (1 byte)] [proto-encoded message body]
Tag Message Category Auth mechanism
0x02 MsgDelegateVote Voting round ZKP #1 + RedPallas
0x03 MsgCastVote Voting round ZKP #2 + RedPallas
0x04 MsgRevealShare Voting round ZKP #3
0x05 MsgSubmitTally Voting round (injected) Proposer identity check
0x08 MsgAckExecutiveAuthorityKey Ceremony (injected) Proposer identity check
0x0D MsgSubmitPartialDecryption Tallying (injected) Proposer identity check
0x0E MsgContributeDKG Ceremony (injected) Proposer identity check

Any transaction whose first byte does not match a custom tag is decoded as a standard Cosmos SDK Tx. Tag 0x0A is deliberately skipped because it collides with the standard Cosmos Tx protobuf encoding (field 1, wire type 2) — this collision is what makes the two decoders unambiguously distinguishable by a single byte peek. Note that raw MsgCreateValidator is blocked by the ante handler for live transactions -- post-genesis validators must use MsgCreateValidatorWithPallasKey instead.

Message Authentication Invariants

Every message has a specific set of auth checks enforced across the ABCI pipeline. The table below lists the complete check set for each message. "PP" = PrepareProposal, "ProcessProp" = ProcessProposal.

Standard Cosmos SDK messages

These flow through the standard ante chain: signature verification (SigVerificationDecorator), then CeremonyValidatorDecorator for validator-gated types.

Message Who can submit Ante checks MsgServer checks
MsgRegisterPallasKey Any bonded validator secp256k1 sig + CeremonyValidatorDecorator (bonded validator gate) Valid Pallas point; no duplicate registration
MsgRotatePallasKey Any bonded validator secp256k1 sig + CeremonyValidatorDecorator (bonded validator gate) Valid Pallas point; existing key required; no in-flight ceremony; new PK globally unique
MsgCreateValidatorWithPallasKey Anyone (becomes a validator) secp256k1 sig; exempt from CeremonyValidatorDecorator Delegates to x/staking CreateValidator; registers Pallas key; rejects duplicates
MsgProposeCoordinatorAction Current coordinator secp256k1 sig; exempt from CeremonyValidatorDecorator Stores the payload and proposer approval; executes automatically if current approvals meet threshold
MsgApproveCoordinatorAction Current coordinator secp256k1 sig; exempt from CeremonyValidatorDecorator Adds one distinct approval; rejects duplicates, non-members, and expired actions; executes automatically at threshold
MsgEndorseRound Mapped endorser address secp256k1 sig Records the endorser's approval for one round
MsgClearRoundEndorsement Mapped endorser address secp256k1 sig Clears that endorser's approval for one round
MsgCreateValidator Blocked post-genesis Ante handler rejects at BlockHeight > 0 N/A — never reaches MsgServer
MsgSend / MsgMultiSend Blocked Not in message whitelist N/A — never reaches MsgServer

MsgCreateVotingSession, MsgUpdateVoteManagers, MsgScheduleUpgrade, MsgCancelUpgrade, MsgSetEndorser, and MsgAuthorizedSend still exist as payload types, but direct external submission is not the authority model. They are embedded in MsgProposeCoordinatorAction and executed only after coordinator approval.

Vote-round messages (custom wire format, ZKP/RedPallas auth)

These use the custom wire format and bypass the Cosmos Tx envelope. Auth is handled by the ValidateVoteTx pipeline in x/vote/ante.

Message Who can submit Ante checks MsgServer checks
MsgDelegateVote Any Zcash note holder (anonymous) ValidateBasic (field sizes); round ACTIVE; gov nullifier uniqueness; RedPallas sig over sighash; ZKP #1 (delegation proof: note ownership, VAN encoding, nc_root, nf_imt_root) Record gov nullifiers; append van_cmx to commitment tree
MsgCastVote Any delegation holder (anonymous) ValidateBasic; round ACTIVE; VAN nullifier uniqueness; RedPallas sig over canonical sighash; ZKP #2 (vote commitment: VAN ownership, ea_pk binding, commitment tree anchor) Record VAN nullifier; append vote_authority_note_new + vote_commitment to tree
MsgRevealShare Any vote holder (anonymous) ValidateBasic; round ACTIVE or TALLYING; share nullifier uniqueness; ZKP #3 (vote share: share ownership, commitment tree anchor) Record share nullifier; HomomorphicAdd enc_share into tally accumulator

Proposer-injected messages (custom wire format, proposer identity auth)

These are auto-injected by PrepareProposal and cannot be submitted through the mempool (CheckTx/ReCheckTx reject them). Auth is enforced at three layers: ante handler (per-tag dispatch), ProcessProposal (non-proposer validators verify before accepting a block), and MsgServer (FinalizeBlock execution).

Message Ante check ProcessProposal check MsgServer check
MsgContributeDKG ValidateProposerIsCreator (mempool block + creator == proposer) Round PENDING + REGISTERING; payload count matches; creator is ceremony validator; creator == proposer ValidateProposerIsCreator; round PENDING + REGISTERING; creator in ceremony validators; payload count matches; Feldman commitment count matches threshold
MsgAckExecutiveAuthorityKey ValidateProposerIsCreator (mempool block + creator == proposer) Round PENDING + DEALT; creator is ceremony validator; no duplicate ack; creator == proposer ValidateProposerIsCreator; round PENDING + DEALT; creator in ceremony validators; no duplicate ack
MsgSubmitPartialDecryption ValidateProposerIsCreator (mempool block + creator == proposer) Round TALLYING + threshold > 0; creator is ceremony validator; ValidatorIndex == ShamirIndex; no duplicate; creator == proposer ValidateProposerIsCreator; round TALLYING + threshold > 0; creator in ceremony validators; ValidatorIndex == ShamirIndex; no duplicate; entries are valid Pallas points
MsgSubmitTally ValidateVoteTxValidateProposerIsCreator (mempool block + creator == proposer) Round TALLYING; creator == proposer ValidateProposerIsCreator; round TALLYING; verify each entry against on-chain accumulators (DLEQ proof in legacy mode, Lagrange re-derivation in threshold mode); transition to FINALIZED

Key design invariant

ValidateProposerIsCreator is the unified proposer identity check shared by all four injected message types. It enforces two properties:

  1. Mempool exclusion: IsCheckTx() || IsReCheckTx() returns an error, preventing external submission.
  2. Proposer binding: During FinalizeBlock, msg.Creator must equal the operator address of the validator whose consensus key matches BlockHeader.ProposerAddress. This prevents a malicious proposer from injecting messages on behalf of other validators.

Message Whitelist and Ante Handler Design

Standard Cosmos transactions pass through a two-layer gate before reaching the decorator chain. Both layers are in app/ante.go and app/ante_whitelist.go.

Layer 1: Pre-filter in NewDualAnteHandler (before any decorator runs)

  1. Single-message only. Multi-message transactions are rejected unconditionally. This eliminates the noop-signer attack class where a zero-signer message (e.g. MsgRevealShare) piggybacks on a legitimately-signed carrier message.
  2. Vote/ceremony messages blocked (defense-in-depth). isVoteModuleMsg rejects MsgDelegateVote, MsgCastVote, MsgRevealShare, MsgContributeDKG, MsgAckExecutiveAuthorityKey, MsgSubmitPartialDecryption, and MsgSubmitTally. These must enter via the custom VoteTxWrapper path where ZKP/RedPallas verification runs. The whitelist (Layer 2) would also catch these, but this explicit type check fires earlier and produces a more actionable error.
  3. MsgCreateValidator blocked post-genesis. At BlockHeight > 0, raw MsgCreateValidator is rejected — validators must use MsgCreateValidatorWithPallasKey to atomically register a Pallas key. The message is allowed at height 0 for genesis gentx bootstrapping.

Layer 2: MessageWhitelistDecorator (inside the standard ante chain)

A positive-security allowlist: only messages whose proto type URL appears in DefaultAllowedMessages() are accepted. Any new message type must be explicitly added here before it can be processed. The current allowed set is:

Module Allowed messages
Vote MsgProposeCoordinatorAction, MsgApproveCoordinatorAction, MsgRegisterPallasKey, MsgRotatePallasKey, MsgCreateValidatorWithPallasKey, MsgEndorseRound, MsgClearRoundEndorsement
Staking MsgCreateValidator (genesis only — blocked post-genesis by Layer 1), MsgEditValidator
Slashing MsgUnjail

Explicitly excluded (and the reasons):

Excluded messages Reason
MsgSend, MsgMultiSend Replaced by coordinator-approved MsgAuthorizedSend payloads. Unrestricted transfers would let anyone accumulate stake and create a validator, bypassing the controlled validator set.
MsgDelegate, MsgUndelegate, MsgBeginRedelegate Prevents validators from reorganizing stake without a vote manager. Initial self-delegation is handled atomically by MsgCreateValidatorWithPallasKey.
MsgWithdrawDelegatorReward, MsgWithdrawValidatorCommission Prevents extracting staking rewards as liquid tokens that could be transferred outside of vote-manager control.
MsgFundCommunityPool, MsgSetWithdrawAddress, MsgUpdateParams No governance module; these have no legitimate use on this chain.
Coordinator-owned vote payloads MsgCreateVotingSession, MsgUpdateVoteManagers, MsgScheduleUpgrade, MsgCancelUpgrade, MsgSetEndorser, and MsgAuthorizedSend are not public Msg RPCs and must be executed through MsgProposeCoordinatorAction.
All vote/ceremony ZKP messages Must use the custom VoteTxWrapper wire format with ZKP/RedPallas authentication. Blocked by both Layer 1 and Layer 2.

MsgAuthorizedSend authorization rules

MsgAuthorizedSend is the only coin-transfer payload on this chain, and it is only executable through coordinator action approval. Authorization is enforced in the MsgServer handler (x/vote/keeper/msg_server_send.go):

  • Coordinator-funded sends must go through coordinator action approval. The source funding account must be one of the approving coordinators.
  • MsgAuthorizedSend is a coordinator action payload, not a public Msg RPC.
  • All non-coordinator source accounts are rejected.

Design assumptions

  1. The coordinator policy controls coordinator-funded stake distribution. Validators receive tokens via coordinator-approved MsgAuthorizedSend payloads and bond them during MsgCreateValidatorWithPallasKey. Each coordinator holds their own pre-funded balance; the genesis stake pool is split evenly across the set to preserve total supply.
  2. The whitelist is a compile-time constant. Adding a new message type requires a code change, rebuild, and coordinated chain upgrade.
  3. Custom wire format messages (VoteTxWrapper) are never subject to the whitelist — they are routed to the ZKP/RedPallas validation pipeline before the standard ante chain runs.
  4. If a custom wire format message is somehow removed from isVoteModuleMsg (Layer 1), the whitelist (Layer 2) still blocks it since it is not in DefaultAllowedMessages().

REST API

The chain exposes a JSON REST API alongside CometBFT RPC. Clients POST JSON bodies for transaction submission and GET for queries — no protobuf encoding required on the client side.

Transaction Endpoints (Custom Wire Format)

Vote-round messages use the custom wire format and are submitted as JSON POST requests:

Method Path Description
POST /shielded-vote/v1/delegate-vote Submit a delegation proof (ZKP #1)
POST /shielded-vote/v1/cast-vote Cast an encrypted vote (ZKP #2)
POST /shielded-vote/v1/reveal-share Reveal an encrypted share (ZKP #3)

These endpoints accept JSON, encode the message with the custom wire format, and broadcast via CometBFT's broadcast_tx_sync. MsgSubmitTally, MsgContributeDKG, MsgAckExecutiveAuthorityKey, and MsgSubmitPartialDecryption have no REST endpoints — they are proposer-only and auto-injected via PrepareProposal.

Validator-owned messages (MsgRegisterPallasKey, MsgRotatePallasKey, MsgCreateValidatorWithPallasKey) and coordinator action messages (MsgProposeCoordinatorAction, MsgApproveCoordinatorAction) are standard Cosmos SDK transactions routed through the MsgServiceRouter. Coordinator-owned payloads such as vote creation, manager updates, upgrades, endorser mappings, and coordinator-funded sends are submitted inside coordinator action proposals.

Query Endpoints

Method Path Description
GET /shielded-vote/v1/ceremony Current ceremony state and status
GET /shielded-vote/v1/rounds List all stored vote rounds
GET /shielded-vote/v1/rounds/active Currently active voting round
GET /shielded-vote/v1/round/{round_id} Voting round by hex round ID
GET /shielded-vote/v1/vote-summary/{round_id} Denormalized round summary with proposals
GET /shielded-vote/v1/tally/{round_id}/{proposal_id} Tally for a specific proposal
GET /shielded-vote/v1/tally-results/{round_id} All tally results for a round
GET /shielded-vote/v1/commitment-tree/{round_id}/{height} Vote commitment tree at block height
GET /shielded-vote/v1/commitment-tree/{round_id}/latest Latest vote commitment tree
GET /shielded-vote/v1/commitment-tree/{round_id}/leaves Paginated tree leaves (?from_height=X&to_height=Y)
GET /shielded-vote/v1/pallas-keys All registered Pallas keys
GET /shielded-vote/v1/vote-managers Current coordinator policy
GET /shielded-vote/v1/coordinator-actions Pending coordinator actions
GET /shielded-vote/v1/coordinator-actions/{action_id} Coordinator action by ID
GET /shielded-vote/v1/genesis Chain genesis JSON
GET /shielded-vote/v1/snapshot-data/{height} Nullifier snapshot data at block height
GET /shielded-vote/v1/tx/{hash} Transaction status by hash

from_height and to_height are optional on the commitment-tree leaves endpoint. A zero or omitted from_height starts at the first indexed leaf block for the round. A zero or omitted to_height resolves to the current query height. Responses include blocks[], where each block contains the appended leaves and the 32-byte tree root after that block, plus next_from_height. Clients continue requesting with from_height=next_from_height and the same pinned to_height until next_from_height is zero.

Helper Sentry Observability

The helper server uses Sentry when [helper].sentry_dsn is set in app.toml or SENTRY_DSN is present in the environment. Sentry events include a stage tag that identifies the helper code path that emitted the error, such as enqueue, process_share, leaf_read, helper_new, or tree_status.

When a voting round closes, the helper summarizes queued shares before purging expired witness data. If any shares for that round are still pending or failed, it emits a Sentry error with stage=round_closed_unsubmitted_shares and tags for round_id, total_shares, pending_shares, failed_shares, submitted_shares, and unsubmitted_shares. Configure Sentry issue alerts on that stage tag to page when share data was accepted by the helper but not submitted on-chain before the round closed.

On-Chain State (KV Store Keys)

Key Type Description
0x09 CeremonyState (singleton) EA key ceremony lifecycle
0x0A VoteManagerSet (singleton) Coordinator addresses and threshold
0x01 VoteRound (per round) Voting session state
0x02-0x08 Various Nullifiers, tallies, commitment tree, etc.

CeremonyState Fields

enum CeremonyStatus {
  CEREMONY_STATUS_UNSPECIFIED   = 0;
  CEREMONY_STATUS_REGISTERING   = 1; // Accepting validator pk_i registrations (no timeout)
  CEREMONY_STATUS_DEALT         = 2; // DKG contributions landed, awaiting acks
  CEREMONY_STATUS_CONFIRMED     = 3; // All acked (fast path) or required quorum acked at timeout, ea_pk ready
}

message CeremonyState {
  CeremonyStatus              status        = 1;
  bytes                       ea_pk         = 2;  // Set when DKG contributions complete
  repeated ValidatorPallasKey validators    = 3;  // All registered pk_i
  repeated DealerPayload      payloads      = 4;  // ECIES envelopes from DKG contributions
  repeated AckEntry           acks          = 5;  // Per-validator ack status
  string                      dealer        = 6;  // Validator address of the dealer
  uint64                      phase_start   = 7;  // Unix seconds when current phase started
  uint64                      phase_timeout = 8;  // Timeout in seconds for current phase
}

About

Cosmos SDK application chain for private on-chain voting using Zcash-derived cryptography

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors