Skip to main content

POST /ballots

Broadcasts a transaction to the canonical node. Used for all transaction types (TxTypePollCreate, TxTypeBallotCast, TxTypePollClose, TxTypeConfirmReceipt, TxTypeRegisterValidator). The request body is a JSON-encoded tx.Tx envelope. The canonical state machine performs all authentication and validation; this endpoint does not authenticate the sender. Request body
{
  "type": 2,
  "signature": "<base64-encoded-bytes>",
  "data": { ... }
}
Response (success)
{ "code": 0 }
Response (failure)
{
  "code": 3,
  "log": "tx validation failed: duplicate vote: identity abc... already voted in poll poll-1"
}

Casting a ballot (TxType = 2)

The ballot path supports both the preferred non-ZK tier and the optional ZK tier.

Preferred non-ZK tier

{
  "type": 2,
  "signature": "<voter-device-sig>",
  "data": {
    "poll_id": "referendum-2026-1",
    "choices": ["yes"],
    "ballot_nonce": "<random-32-bytes-hex>",
    "voter_pub_key": "<ed25519-pub-key-hex>",
    "voter_sig": "<base64-ed25519-sig>",
    "didit_device_sig": "<ed25519-sig-base64>",
    "timestamp": 1700040000
  }
}

ZK tier

{
  "type": 2,
  "signature": "<voter-device-sig>",
  "data": {
    "poll_id": "referendum-2026-1",
    "zk_nullifier": "<MiMC(person_secret, poll_id)>",
    "zk_nullifier_proof": "<groth16-proof-base64>",
    "zk_commitment": "<MiMC(person_secret)>",
    "didit_commitment_sig": "<ed25519-sig-base64>",
    "choices": ["yes"],
    "ballot_nonce": "<random-32-bytes-hex>",
    "timestamp": 1700040000
  }
}
Validation sequence in the canonical runtime
  1. tx.Tx.Validate() — stateless: field presence, JSON well-formedness
  2. state.validateBallotCast:
    • In the preferred non-ZK tier: verify voter_sig and provider signature over sha256(voter_pub_key ‖ poll_id)
    • In the ZK tier: verify provider signature over sha256(zk_commitment ‖ poll_id) and verify the Groth16 proof
    • Check the active dedup key not already present in voterRegistry for this poll
  3. state.executeBallotCast:
    • Append VoteRecord to List 1 (keyed by ballot_id = H(ballot_nonce))
    • Append VoterRecord to List 2 (keyed by the active identity hash or nullifier)
    • Assert count-match invariant
Steps 1–2 occur in CheckTx (mempool gate) and again in FinalizeBlock.
Which fields are required depends on the active identity tier. If the runtime is started without a ZK verifier, the non-ZK device-key path is authoritative. If a ZK verifier is loaded, the ZK fields become required.