Documentation
¶
Overview ¶
Package helper implements the share processing pipeline that receives encrypted voting shares from wallets, applies random delays for temporal unlinkability, generates ZKP #3 proofs, and submits MsgRevealShare to the chain.
This package runs inside the svoted binary, reading commitment tree leaves directly from the vote keeper's KV store.
Index ¶
- Variables
- func CaptureErr(err error, tags map[string]string)
- func FlushSentry()
- func InitSentry(dsn, release, serverName string, logger log.Logger) error
- func IsDuplicateNullifier(code uint32) bool
- func RegisterRoutes(router *mux.Router, store *ShareStore, logger log.Logger)
- func RegisterRoutesWithGetters(router *mux.Router, getStore func() *ShareStore, getAPIToken func() string, ...)
- func RegisterRoutesWithStoreGetter(router *mux.Router, getStore func() *ShareStore, logger log.Logger)
- func RunPulse(ctx context.Context, cfg PulseConfig)
- type BroadcastResult
- type ChainSubmitter
- type Config
- type EncryptedShareWire
- type EnqueueResult
- type Helper
- type MsgRevealShareJSON
- type Processor
- type ProofGenerator
- type PulseConfig
- type QueueStatus
- type QueuedShare
- type RoundInfoFetcher
- type RoundStatusChecker
- type ShareNullifierChecker
- type ShareNullifierCheckerGetter
- type SharePayload
- type ShareState
- type ShareStore
- func (s *ShareStore) Close() error
- func (s *ShareStore) Enqueue(payload SharePayload) (EnqueueResult, error)
- func (s *ShareStore) MarkFailed(roundID string, shareIndex, proposalID uint32, treePosition uint64)
- func (s *ShareStore) MarkSubmitted(roundID string, shareIndex, proposalID uint32, treePosition uint64)
- func (s *ShareStore) PurgeExpiredRounds() int64
- func (s *ShareStore) Status() map[string]QueueStatus
- func (s *ShareStore) TakeReady() []QueuedShare
- type SignFn
- type TreeReader
- type TreeStatus
- type VCHashFunc
Constants ¶
This section is empty.
Variables ¶
var ErrInvalidCommitment = fmt.Errorf("invalid vote commitment")
ErrInvalidCommitment is returned when the share's recomputed vote commitment hash does not match the on-chain leaf at the claimed tree position.
var ErrInvalidSubmitAt = errors.New("invalid submit_at")
ErrInvalidSubmitAt is returned when submit_at is in the past or after vote end time.
var ErrUnknownRound = errors.New("unknown voting round")
ErrUnknownRound is returned when a share references a round that does not exist on-chain. Callers can check for this with errors.Is to distinguish it from transient failures.
Functions ¶
func CaptureErr ¶ added in v0.4.2
CaptureErr delegates to the shared sentry package.
func FlushSentry ¶ added in v0.4.2
func FlushSentry()
FlushSentry delegates to the shared sentry package.
func InitSentry ¶ added in v0.4.2
InitSentry delegates to the shared sentry package.
func IsDuplicateNullifier ¶
IsDuplicateNullifier returns true if the chain rejection code matches ErrDuplicateNullifier, meaning the share was already revealed by another helper. In quorum mode multiple helpers process the same share; the first to submit wins and the rest receive this benign rejection.
func RegisterRoutes ¶
func RegisterRoutes(router *mux.Router, store *ShareStore, logger log.Logger)
RegisterRoutes registers helper server HTTP routes on the given mux router.
func RegisterRoutesWithGetters ¶
func RegisterRoutesWithGetters( router *mux.Router, getStore func() *ShareStore, getAPIToken func() string, getExposeQueueStatus func() bool, getTree func() TreeReader, getVCHash func() VCHashFunc, getShareNullifier ShareNullifierCheckerGetter, logger log.Logger, )
RegisterRoutesWithGetters registers helper routes using runtime getters for store, API token, tree reader, VC hash function, and share-nullifier checker.
func RegisterRoutesWithStoreGetter ¶
func RegisterRoutesWithStoreGetter(router *mux.Router, getStore func() *ShareStore, logger log.Logger)
RegisterRoutesWithStoreGetter registers helper server HTTP routes on the given mux router, resolving the store at request time. This allows routes to be mounted before the helper is fully initialized.
func RunPulse ¶
func RunPulse(ctx context.Context, cfg PulseConfig)
RunPulse registers with register-validator on startup (to ensure approved-servers is populated), then sends a heartbeat pulse to server-heartbeat every 2 hours until ctx is cancelled.
If the initial registration returns "pending" (e.g. the admin server hasn't seen the bonding tx yet), registration is retried on each tick until it succeeds, then the loop switches to heartbeat-only mode.
Types ¶
type BroadcastResult ¶
type BroadcastResult struct {
TxHash string `json:"tx_hash"`
Code uint32 `json:"code"`
Log string `json:"log"`
}
BroadcastResult is the chain's response to a transaction broadcast.
type ChainSubmitter ¶
type ChainSubmitter struct {
// contains filtered or unexported fields
}
ChainSubmitter submits MsgRevealShare transactions to the chain's REST API.
func NewChainSubmitter ¶
func NewChainSubmitter(baseURL string) *ChainSubmitter
NewChainSubmitter creates a submitter targeting the given base URL.
func (*ChainSubmitter) FetchVoteRound ¶
func (c *ChainSubmitter) FetchVoteRound(roundID string) (uint64, error)
FetchVoteRound queries the chain REST API for a vote round's metadata and returns the vote_end_time (unix seconds).
func (*ChainSubmitter) SubmitRevealShare ¶
func (c *ChainSubmitter) SubmitRevealShare(msg *MsgRevealShareJSON) (*BroadcastResult, error)
SubmitRevealShare POSTs a MsgRevealShare to the chain endpoint.
type Config ¶
type Config struct {
// Disable turns off the helper server entirely.
Disable bool `mapstructure:"disable"`
// APIToken protects POST /shielded-vote/v1/shares when set (checked via X-Helper-Token).
APIToken string `mapstructure:"api_token"`
// ExposeQueueStatus enables the benchmark-only GET /shielded-vote/v1/queue-status
// endpoint. Keep this off by default to avoid exposing per-round queue
// counts to observers.
ExposeQueueStatus bool `mapstructure:"expose_queue_status"`
// DBPath is the path to the SQLite database file. Use ":memory:" for testing.
DBPath string `mapstructure:"db_path"`
// ProcessInterval is how often to check for shares ready to submit (seconds).
ProcessInterval int `mapstructure:"process_interval"`
// ChainAPIPort is the port of the chain's REST API (localhost).
// Used for submitting MsgRevealShare via POST. Defaults to 1317 — the
// standard Cosmos SDK [api] address port that svoted serves. Setting
// this to a port where no server listens causes every share to fail
// broadcast silently (`connection refused` warnings in the log while
// the helper keeps accepting and proving new shares), which surfaces
// to voters as zero-value tallies with no user-visible error.
ChainAPIPort int `mapstructure:"chain_api_port"`
// MaxConcurrentProofs limits concurrent proof generation goroutines.
MaxConcurrentProofs int `mapstructure:"max_concurrent_proofs"`
// AdminURL is the base URL of the admin server for registration and
// heartbeat (e.g. "https://admin.example.com"). When empty, heartbeat
// is disabled.
AdminURL string `mapstructure:"admin_url"`
// HelperURL is this server's own public URL as seen by clients
// (e.g. "https://1-2-3-4.sslip.io"). When empty, heartbeat is disabled.
HelperURL string `mapstructure:"helper_url"`
// SentryDSN is the Sentry DSN for error tracking. When empty, Sentry is
// disabled. Can also be set via the SENTRY_DSN environment variable at
// runtime (app.toml takes precedence if set).
SentryDSN string `mapstructure:"sentry_dsn"`
}
Config holds the helper server configuration, read from app.toml helper.
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig returns the default helper configuration.
type EncryptedShareWire ¶
type EncryptedShareWire struct {
}
EncryptedShareWire is the wire format for an encrypted ElGamal share component.
type EnqueueResult ¶
type EnqueueResult int
EnqueueResult reports how an enqueue attempt was handled.
const ( EnqueueInserted EnqueueResult = iota EnqueueDuplicate EnqueueConflict )
type Helper ¶
type Helper struct {
Store *ShareStore
Processor *Processor
APIToken string
ExposeQueueStatus bool
VCHash VCHashFunc
Logger log.Logger
}
Helper manages the share processing pipeline lifecycle.
func New ¶
func New(cfg Config, tree TreeReader, prover ProofGenerator, roundFetcher RoundInfoFetcher, isRoundActive RoundStatusChecker, vcHash VCHashFunc, shareNF ShareNullifierChecker, homeDir string, logger log.Logger) (*Helper, error)
New creates a new Helper from the given configuration.
Parameters:
- cfg: helper configuration (from app.toml helper section)
- tree: accesses the commitment tree (status + merkle paths + leaf reads) from the keeper's KV store
- prover: generates ZKP #3 proofs (real FFI or mock)
- roundFetcher: queries the chain for round metadata (direct keeper access)
- isRoundActive: checks if a round is still ACTIVE (nil = skip check)
- vcHash: computes vote commitment Poseidon hash for ingress validation
- homeDir: the chain's home directory (for default DB path)
- logger: module logger
func (*Helper) RegisterRoutes ¶
RegisterRoutes registers the helper's HTTP routes on the given router.
func (*Helper) Start ¶
Start launches the background processor in the given context. It blocks until the context is cancelled.
func (*Helper) Tree ¶
func (h *Helper) Tree() TreeReader
Tree returns the tree reader used by the processor.
type MsgRevealShareJSON ¶
type MsgRevealShareJSON struct {
}
MsgRevealShareJSON is the JSON payload for submitting MsgRevealShare to the chain's REST API. Byte fields are base64-encoded (Go's default for []byte).
type Processor ¶
type Processor struct {
// contains filtered or unexported fields
}
Processor is the background share processing loop. It checks the share queue at Poisson-distributed intervals (exponential inter-arrival times) for shares whose delay has elapsed, generates Merkle paths and ZKP #3 proofs, and submits MsgRevealShare to the chain. The random timing prevents an observer from correlating submission patterns with share readiness.
func NewProcessor ¶
func NewProcessor( store *ShareStore, tree TreeReader, prover ProofGenerator, submitter *ChainSubmitter, logger log.Logger, meanInterval time.Duration, maxConcurrent int, isRoundActive RoundStatusChecker, ) *Processor
NewProcessor creates a new share processor.
type ProofGenerator ¶
type ProofGenerator interface {
// merklePath: 772-byte serialized Merkle path (from votetree.TreeHandle.Path)
// shareComms: 16 × 32-byte per-share Poseidon commitments
// primaryBlind: 32-byte blind factor for the revealed share
// encC1, encC2: 32-byte compressed Pallas points of the revealed share
// Returns proof bytes, nullifier (32 bytes), tree root (32 bytes).
GenerateShareRevealProof(
merklePath []byte,
shareComms [16][32]byte,
primaryBlind [32]byte,
encC1 [32]byte,
encC2 [32]byte,
shareIndex uint32,
proposalID, voteDecision uint32,
roundID [32]byte,
) (proof []byte, nullifier [32]byte, treeRoot [32]byte, err error)
}
ProofGenerator abstracts ZKP #3 proof generation for testing.
type PulseConfig ¶
type PulseConfig struct {
AdminURL string // Admin server base URL (e.g. "https://admin.example.com")
HelperURL string // Own public URL (e.g. "https://1-2-3-4.sslip.io")
OperatorAddress string // Bech32 operator address derived from validator key
Moniker string // Node moniker from CometBFT config
Sign SignFn
Logger log.Logger
}
PulseConfig holds the parameters needed for the heartbeat loop.
type QueueStatus ¶
type QueueStatus struct {
Total int `json:"total"`
Pending int `json:"pending"`
Submitted int `json:"submitted"`
Failed int `json:"failed"`
}
QueueStatus holds per-round queue statistics.
type QueuedShare ¶
type QueuedShare struct {
}
QueuedShare is a share payload with processing metadata.
type RoundInfoFetcher ¶
RoundInfoFetcher queries the chain for vote round metadata. Returns vote_end_time (unix seconds) for the given round ID (hex).
type RoundStatusChecker ¶ added in v0.3.0
RoundStatusChecker returns true if the round is still accepting shares (i.e., status == ACTIVE). Used by the processor to skip shares for rounds that have transitioned to TALLYING or beyond, avoiding wasted ZKP computation.
type ShareNullifierChecker ¶ added in v0.3.0
ShareNullifierChecker reports whether a share nullifier is recorded on-chain for the given voting round (hex-encoded 32-byte round id). Used by the share-status endpoint to confirm MsgRevealShare acceptance without exposing a public chain query API to wallets.
type ShareNullifierCheckerGetter ¶ added in v0.3.0
type ShareNullifierCheckerGetter func() ShareNullifierChecker
ShareNullifierCheckerGetter resolves the checker at request time (nil when helper disabled).
type SharePayload ¶
type SharePayload struct {
}
SharePayload is the wire format sent by wallets to the helper server.
type ShareState ¶
type ShareState int
ShareState represents the processing state of a queued share.
const ( )
type ShareStore ¶
type ShareStore struct {
// contains filtered or unexported fields
}
ShareStore is a SQLite-backed share queue with ephemeral in-memory scheduling. Payload data and processing state are persisted; submit_at timestamps from the client control when each share is released for proof generation.
func NewShareStore ¶
func NewShareStore(dbPath string, fetcher RoundInfoFetcher) (*ShareStore, error)
NewShareStore opens (or creates) a SQLite database and runs migrations.
func (*ShareStore) Enqueue ¶
func (s *ShareStore) Enqueue(payload SharePayload) (EnqueueResult, error)
Enqueue adds a share payload with a uniform random submission delay, capped at the vote end time for the round.
Returns:
- EnqueueInserted when a new row was inserted and scheduled.
- EnqueueDuplicate when an identical payload already exists.
- EnqueueConflict when an entry exists for (round_id, share_index) but with different payload content.
func (*ShareStore) MarkFailed ¶
func (s *ShareStore) MarkFailed(roundID string, shareIndex, proposalID uint32, treePosition uint64)
MarkFailed marks a share processing attempt as failed, with retry or permanent failure after max attempts.
func (*ShareStore) MarkSubmitted ¶
func (s *ShareStore) MarkSubmitted(roundID string, shareIndex, proposalID uint32, treePosition uint64)
MarkSubmitted marks a share as successfully submitted to the chain.
func (*ShareStore) PurgeExpiredRounds ¶
func (s *ShareStore) PurgeExpiredRounds() int64
PurgeExpiredRounds deletes all share data for rounds whose vote_end_time has passed, and removes the corresponding entries from the in-memory schedule and round cache. Returns the number of rows deleted.
func (*ShareStore) Status ¶
func (s *ShareStore) Status() map[string]QueueStatus
Status returns per-round queue statistics.
func (*ShareStore) TakeReady ¶
func (s *ShareStore) TakeReady() []QueuedShare
TakeReady returns all shares past their scheduled submission time that are in Received state, transitioning them to Witnessed.
type SignFn ¶
SignFn signs an arbitrary string payload using ADR-036 and returns the base64-encoded signature and compressed public key.
type TreeReader ¶
type TreeReader interface {
// SetRoundID selects the voting round for subsequent tree lookups.
SetRoundID(roundID []byte)
// GetTreeStatus returns lightweight tree statistics (leaf count + anchor height).
GetTreeStatus() (TreeStatus, error)
// MerklePath returns the 772-byte serialized Poseidon Merkle authentication
// path for the leaf at position, anchored to the checkpoint at anchorHeight.
// anchorHeight must correspond to a checkpoint that exists in the persistent
// tree (i.e. a block height at which Checkpoint was called by EndBlocker).
MerklePath(position uint64, anchorHeight uint32) ([]byte, error)
// LeafAt returns the raw 32-byte vote commitment stored at the given tree
// position, or nil if no leaf exists at that index.
LeafAt(position uint64) ([]byte, error)
}
TreeReader abstracts commitment tree access from the keeper. Commitment trees are scoped per voting round; callers must call SetRoundID before any tree lookup to select the correct round.
type TreeStatus ¶
type TreeStatus struct {
LeafCount uint64 `json:"leaf_count"`
AnchorHeight uint64 `json:"anchor_height"`
}
TreeStatus holds lightweight commitment tree statistics.
type VCHashFunc ¶
type VCHashFunc func(roundID, sharesHash [32]byte, proposalID, voteDecision uint32) ([32]byte, error)
VCHashFunc computes the vote commitment Poseidon hash. The implementation is provided by the votecommitment CGo package but abstracted here so the helper package doesn't depend on the Rust FFI library directly.