Electoral Commission daemon for the Criptocracia trustless voting system.
See .specify/memory/constitution.md for governing principles.
See .specify/specs/001-ec-core.md for the feature specification.
See .specify/specs/001-ec-core-plan.md for the technical plan.
cargo build
cargo test
cargo clippy --all-targets --all-features -- -D warnings
cargo fmt
sqlx migrate run # run SQLite migrations- Rust: 1.94.0 (stable, edition 2024)
- blind-rsa-signatures: 0.17.1 (nonce is
[u8; 32], NOT BigUint) - sha2: 0.11.0-rc.5 (rc required — blind-rsa 0.17.1 needs digest ^0.11)
- rand: 0.10.0
- nostr-sdk: 0.44.1
- sqlx: 0.8.6 (stable — do NOT use 0.9.x alpha)
- tonic/tonic-build: 0.14.5
- prost: 0.14.3
- secrecy: 0.10 (wrap all secret values in
SecretString) - dotenvy: 0.15 (load
.envin development)
- Never compromise voter anonymity. The EC must never store a link between a vote and a voter identity. See Principle 1 in the constitution.
- Use
blind-rsa-signatures = "0.17.1". Version 0.15.2 does not compile on Rust 1.86+ (rsa 0.8 derive bug). The API change: nonces are[u8; 32]notBigUint. Do not usenum-bigint-digfor nonces. - All DB writes that touch
registration_tokensorauthorized_votersMUST be wrapped in a transaction withrows_affected()checks. Race conditions here break the protocol. - No
unwrap()in production code paths. Use?andanyhow::Result. - All voter↔EC messages go through NIP-59 Gift Wrap. No plaintext Nostr messages to/from voters.
tracingfor all logging — notprintln!, notlog::info!.- Secrets never appear in logs. RSA private keys,
NOSTR_PRIVATE_KEY, andEC_DB_PASSWORDmust be wrapped inSecretStringfrom thesecrecycrate. Never call.expose_secret()outside the specific call sites that need it. Never log them even attracing::debug!level. - Config uses the hybrid pattern: non-secret values from
ec.toml(with env var override), secrets from env vars only. See "Config Architecture" in the plan. Load.envviadotenvy::dotenv()at startup for dev convenience. .env,*.pem, andec.db*must be in.gitignore. Commitec.toml(no secrets) and.env.example(template only). Never commit the actual.env.cargo clippy -- -D warningsmust pass clean before any PR.- The gRPC admin API binds to
127.0.0.1by default. External binding requires explicitGRPC_BINDenv var orec.tomloverride. - Follow the task phases in order: Foundation → Rules & Counting → Crypto → Nostr → Handlers → gRPC → Scheduler → Polish.
- Every counting algorithm MUST implement
CountingAlgorithmtrait fromsrc/counting/mod.rs. No ad-hoc counting logic outside this module. ElectionRulesis loaded fresh from the.tomlfile on eachAddElectioncall (no caching). Never hardcode rule values in Rust code — always read from the loadedElectionRulesstruct.- Ballot validation happens in
handlers/cast_vote.rsbefore touching the DB. Usevalidate_ballot()against the election's loaded rules. A ballot that violatesmin_choices/max_choicesor contains invalid candidate IDs MUST be rejected before any DB write. candidate_idsin thevotestable is a JSON TEXT array ([3]or[3,1,4,2]). Never use a single integer column for votes — it breaks STV ranked ballots.- Adding a new counting method = implement
CountingAlgorithm+ register inalgorithm_for()+ add a.tomlinrules/. No other files need to change. - Add language identifiers to fenced code blocks (e.g.
```rust,```sql,```text) to satisfy markdown linting (MD040).