voting

package
v0.9.14 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 18, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

README

Blind voting

Reference app: an anonymous-ballot, sum-weighted tally with no FHE. Uses keygen.PlaintextKeygen (no-op keygen that satisfies the pipeline's topology requirements) and a plain integer-sum tally. Threat model: trusted orchestrator, anonymous ballots between submission and tally.

What it demonstrates

  • ARES app that doesn't need FHE — the framework's value here is the state-machine + transport plumbing, not the encrypted scoring.
  • keygen.PlaintextKeygen as the canonical no-crypto keygen topology.
  • v0.4.0 SC-10 ciphertext lineage protecting non-FHE byte payloads: lineage binds each ballot's plaintext bytes to its submitter so the orchestrator cannot swap ballots between collection and tally. Demonstrates SC-10 is a binding-over- bytes primitive, not a binding-over-ciphertexts primitive.

Pipelines

  • Pipeline() — plaintext, no anonymity (trusted orchestrator).
  • PipelineWithLineage(...) — adds SC-10 ballot binding.
  • PipelineWithShuffle(...) — adds inter-participant slot anonymity via pkg/ares/phase/anon so the authority cannot link an anonymized ballot slot to the voter. See that package's godoc for the SC-7 collusion bound (certain deanonymization needs k >= N-2 colluders).
Invite → PlaintextKeygen → SubmitVote → Tally → Settle

Usage

Legacy (no lineage):

runner, err := voting.Pipeline()

v0.4.0 lineage variant:

signer, _   := sign.NewEd25519Signer()
verifiers   := map[string]sign.Signer{sign.Ed25519Algorithm: signer}
runner, err := voting.PipelineWithLineage(signer, verifiers)

Each ballot becomes a signed DAGNode whose PayloadHash covers the plaintext vote bytes. A tampered ballot fails verify at the runner before reaching PhaseTally.

Tamper-detection smoke

tamper_test.go — the canonical demonstration that SC-10 protects byte payloads regardless of whether they are FHE ciphertexts. Tampers a signed vote and asserts *lineage.MismatchError.

Running as a service

Unlike the other three examples, voting does not ship a cmd/session-service. Consumers wire the runner into their own HTTP+WebSocket service using transport.NewService directly — the auction or ride-share session-service files are reasonable templates.

References

Documentation

Overview

Package voting is a worked example showing how to build a non-FHE application on top of the ARES framework. Voters submit signed weighted ballots; the orchestrator tallies the weighted sum and publishes a signed transcript with the result.

The pipeline reuses the default state vocabulary (INVITING, LOCKED, GOSSIP, SCORING, DECRYPTING, BROADCASTING) so it can drop in keygen.PlaintextKeygen (which owns LOCKED -> GOSSIP) unchanged. There is no cryptographic privacy beyond transport-layer signing — this example exists to demonstrate that the ARES phase-composition machinery works for protocols whose scoring shape is "sum the inputs" rather than "FHE argmax of encrypted scores".

Pipeline:

PhaseInvite      INVITING     -> LOCKED         (server-side, defaults shape)
keygen.Plaintext LOCKED       -> GOSSIP         (no-crypto keygen)
PhaseSubmitVote  GOSSIP       -> SCORING        (accumulate ballots)
PhaseTally       SCORING      -> DECRYPTING     (compute weighted sum)
PhaseSettle      DECRYPTING   -> (terminal / StateNone)   (emit signed transcript)

Index

Constants

View Source
const (
	CtxVoteParticipants = "vote.participants"
	CtxVoteBallots      = "vote.ballots"    // map[string]Ballot
	CtxVoteTally        = "vote.tally"      // Tally
	CtxVoteTranscript   = "vote.transcript" // signed transcript bytes
)

SessionContext keys produced and consumed by the voting pipeline.

Variables

This section is empty.

Functions

func Pipeline

func Pipeline() (*phase.SessionRunner, error)

Pipeline builds the voting SessionRunner:

Invite -> PlaintextKeygen -> SubmitVote -> Tally -> Settle

No FHE; PlaintextKeygen is a documented no-op that fills the LOCKED -> GOSSIP arc without producing crypto material. Use this example as a template for applications whose threat model trusts the orchestrator (regulated governance, internal voting, etc.).

func PipelineWithLineage added in v0.4.0

func PipelineWithLineage(signer sign.Signer, peerVerifiers map[string]sign.Signer) (*phase.SessionRunner, error)

PipelineWithLineage builds the voting pipeline with SC-10 ciphertext lineage enabled. Ballots are not FHE-encrypted in this example (PlaintextKeygen topology) but lineage still binds each ballot's bytes to its submitter — preventing the election authority from swapping ballots between collection and tally. Demonstrates SC-10 is useful for non-FHE ARES apps too: the binding is over byte payloads, not over cryptographic objects specifically.

func PipelineWithShuffle added in v0.5.0

func PipelineWithShuffle(signer sign.Signer, peerVerifiers map[string]sign.Signer) (*phase.SessionRunner, error)

PipelineWithShuffle builds the voting pipeline with inter-participant slot anonymity: an onion-shuffle gossip round runs between keygen and ballot submission so the election authority cannot link an anonymized slot to the voter who produced it. Demonstrates the generic pkg/ares/phase/anon primitive on a non-FHE app.

Arc: Invite -> PlaintextKeygen -> Shuffle(GOSSIP->VERIFYING)

-> Verify(VERIFYING->SUBMITTING) -> SubmitVote(SUBMITTING->SCORING)
-> Tally -> Settle.

Types

type Ballot

type Ballot struct {
	Voter  string  `json:"voter"`
	Choice int     `json:"choice"`
	Weight float64 `json:"weight"`
}

Ballot is one voter's submission. Weight allows weighted voting (e.g. share-weighted governance). Choice is the integer choice index — the application defines what each index means.

type PhaseInvite

type PhaseInvite struct{}

PhaseInvite opens a voting session. The orchestrator picks the eligible voter set and emits a `vote.invitation` frame to each. Identical shape to `defaults.Phase1aSessionInitiation` — the only difference is the message type and that no crypto contract is declared (plaintext keygen carries no CKKS parameters).

func NewPhaseInvite

func NewPhaseInvite() *PhaseInvite

func (PhaseInvite) CheckComplete

func (PhaseInvite) CheckComplete(*phase.SessionContext) bool

func (PhaseInvite) ConsumedMessageTypes

func (PhaseInvite) ConsumedMessageTypes() []string

func (PhaseInvite) Enter

func (PhaseInvite) EntryState

func (PhaseInvite) EntryState() phase.SessionState

func (PhaseInvite) Exit

func (PhaseInvite) ExitState

func (PhaseInvite) ExitState() phase.SessionState

func (PhaseInvite) InternalStates

func (PhaseInvite) InternalStates() []phase.SessionState

func (PhaseInvite) Lifetime

func (PhaseInvite) Lifetime() phase.Lifetime

func (PhaseInvite) Name

func (PhaseInvite) Name() string

func (PhaseInvite) OnMessage

func (PhaseInvite) Provides

func (PhaseInvite) Provides() phase.ContextSchema

func (PhaseInvite) Requires

func (PhaseInvite) Requires() phase.ContextSchema

func (PhaseInvite) RunsAt

func (PhaseInvite) RunsAt() phase.RunsAt

type PhaseSettle

type PhaseSettle struct{}

PhaseSettle emits a signed transcript carrying the tally. Closely mirrors `sealed_bid_auction.PhaseSettlement` shape: synchronous Enter that writes a transcript artifact, ExitState terminal.

func NewPhaseSettle

func NewPhaseSettle() *PhaseSettle

func (PhaseSettle) CheckComplete

func (PhaseSettle) CheckComplete(*phase.SessionContext) bool

func (PhaseSettle) ConsumedMessageTypes

func (PhaseSettle) ConsumedMessageTypes() []string

func (PhaseSettle) Enter

func (PhaseSettle) Enter(ctx *phase.SessionContext) error

func (PhaseSettle) EntryState

func (PhaseSettle) EntryState() phase.SessionState

func (PhaseSettle) Exit

func (PhaseSettle) ExitState

func (PhaseSettle) ExitState() phase.SessionState

func (PhaseSettle) InternalStates

func (PhaseSettle) InternalStates() []phase.SessionState

func (PhaseSettle) Lifetime

func (PhaseSettle) Lifetime() phase.Lifetime

func (PhaseSettle) Name

func (PhaseSettle) Name() string

func (PhaseSettle) OnMessage

func (PhaseSettle) Provides

func (PhaseSettle) Provides() phase.ContextSchema

func (PhaseSettle) Requires

func (PhaseSettle) Requires() phase.ContextSchema

func (PhaseSettle) RunsAt

func (PhaseSettle) RunsAt() phase.RunsAt

type PhaseSubmitVote

type PhaseSubmitVote struct {
	// contains filtered or unexported fields
}

PhaseSubmitVote accumulates one `vote.ballot` from each participant. Wire shape:

{"choice": 1, "weight": 1.5}

The voter's identity is taken from the WS pseudonym, not the payload (preventing cross-voter ballot stuffing under the same account).

entryState defaults to GOSSIP; when the shuffle phases are composed ahead of it, the pipeline re-points it to SUBMITTING.

func NewPhaseSubmitVote

func NewPhaseSubmitVote() *PhaseSubmitVote

NewPhaseSubmitVote returns a submit phase entering at GOSSIP (the no-shuffle pipeline). Use NewPhaseSubmitVoteAt to re-point it.

func NewPhaseSubmitVoteAt added in v0.5.0

func NewPhaseSubmitVoteAt(entry phase.SessionState) *PhaseSubmitVote

NewPhaseSubmitVoteAt returns a submit phase entering at the given state (used when the onion-shuffle arc precedes it).

func (PhaseSubmitVote) CheckComplete

func (PhaseSubmitVote) CheckComplete(ctx *phase.SessionContext) bool

func (PhaseSubmitVote) ConsumedMessageTypes

func (PhaseSubmitVote) ConsumedMessageTypes() []string

func (PhaseSubmitVote) Enter

func (PhaseSubmitVote) EntryState

func (p PhaseSubmitVote) EntryState() phase.SessionState

func (PhaseSubmitVote) Exit

func (PhaseSubmitVote) ExitState

func (PhaseSubmitVote) ExitState() phase.SessionState

func (PhaseSubmitVote) InternalStates

func (PhaseSubmitVote) InternalStates() []phase.SessionState

func (PhaseSubmitVote) Lifetime

func (PhaseSubmitVote) Lifetime() phase.Lifetime

func (PhaseSubmitVote) Name

func (PhaseSubmitVote) Name() string

func (PhaseSubmitVote) OnMessage

func (PhaseSubmitVote) OnMessage(ctx *phase.SessionContext, _, from string, payload []byte) error

func (PhaseSubmitVote) Provides

func (PhaseSubmitVote) Provides() phase.ContextSchema

func (PhaseSubmitVote) Requires

func (PhaseSubmitVote) Requires() phase.ContextSchema

func (PhaseSubmitVote) RunsAt

func (PhaseSubmitVote) RunsAt() phase.RunsAt

type PhaseTally

type PhaseTally struct{}

PhaseTally sums weights per choice and picks the largest. This is the voting equivalent of an FHE app's PhaseArgmax — same pipeline arc (SCORING -> DECRYPTING) but plaintext math.

func NewPhaseTally

func NewPhaseTally() *PhaseTally

func (PhaseTally) CheckComplete

func (PhaseTally) CheckComplete(*phase.SessionContext) bool

func (PhaseTally) ConsumedMessageTypes

func (PhaseTally) ConsumedMessageTypes() []string

func (PhaseTally) Enter

func (PhaseTally) Enter(ctx *phase.SessionContext) error

func (PhaseTally) EntryState

func (PhaseTally) EntryState() phase.SessionState

func (PhaseTally) Exit

func (PhaseTally) ExitState

func (PhaseTally) ExitState() phase.SessionState

func (PhaseTally) InternalStates

func (PhaseTally) InternalStates() []phase.SessionState

func (PhaseTally) Lifetime

func (PhaseTally) Lifetime() phase.Lifetime

func (PhaseTally) Name

func (PhaseTally) Name() string

func (PhaseTally) OnMessage

func (PhaseTally) Provides

func (PhaseTally) Provides() phase.ContextSchema

func (PhaseTally) Requires

func (PhaseTally) Requires() phase.ContextSchema

func (PhaseTally) RunsAt

func (PhaseTally) RunsAt() phase.RunsAt

type Tally

type Tally struct {
	Totals       map[int]float64 `json:"totals"` // choice -> sum(weights)
	TotalBallots int             `json:"total_ballots"`
	WinnerChoice int             `json:"winner_choice"`
	WinnerWeight float64         `json:"winner_weight"`
}

Tally is the result of summing weighted choices. WinnerChoice is the choice with the largest weighted sum.

Directories

Path Synopsis
cmd
session-service command
Command session-service runs the voting example (onion-shuffle + SC-10 lineage variant) as a standalone HTTP+WebSocket service.
Command session-service runs the voting example (onion-shuffle + SC-10 lineage variant) as a standalone HTTP+WebSocket service.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL