Disclaimer: The author is NOT a cryptographer and this work has not been reviewed. This means that there is very likely a fatal flaw somewhere. Criptocracia is still experimental and not production-ready.
Criptocracia is an experimental, trustless open-source electronic voting system built in Rust. It leverages blind RSA signatures to ensure vote secrecy, voter anonymity and integrity, and uses the Nostr protocol for decentralized, encrypted message transport.
- Rust 1.86.0 or later
- OpenSSL (for RSA key generation)
- SQLite development libraries
- Protocol Buffers compiler (for gRPC)
# Clone the repository
git clone https://github.com/grunch/criptocracia.git
cd criptocracia
# Install system dependencies (Ubuntu/Debian)
sudo apt update
sudo apt install -y cmake build-essential libsqlite3-dev pkg-config libssl-dev protobuf-compiler ca-certificates
# Build the project
cargo build --release-
Generate RSA keys (required for blind signatures):
# Generate RSA private key (2048 bits) openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out ec_private.pem # Extract public key openssl rsa -in ec_private.pem -pubout -out ec_public.pem
-
Set environment variables:
# Required: Nostr private key for EC identity export NOSTR_PRIVATE_KEY="your_nostr_private_key_here" # Optional: RSA keys (falls back to files in current directory) export EC_PRIVATE_KEY="$(cat ec_private.pem)" export EC_PUBLIC_KEY="$(cat ec_public.pem)" # Optional: gRPC server binding (default: 127.0.0.1 for localhost only) export GRPC_BIND_IP="0.0.0.0" # For external access
-
Start the Electoral Commission:
./target/release/ec
-
Configure voter settings:
mkdir -p ~/.voter # Edit ~/.voter/settings.toml with your configuration
-
Start the voter client:
./target/release/voter
The EC provides a gRPC API for election management on port 50001:
# Run the example gRPC client
cargo run --example grpc_client --bin ecAvailable operations:
- AddElection: Create new elections
- AddCandidate: Add candidates to elections
- AddVoter: Register voters for specific elections
- CancelElection: Cancel ongoing elections
- GetElection: Retrieve election details
- ListElections: List all elections
- ListVoters: List voters for an election
The critical need for secure, transparent, and anonymous electronicβvoting systems is becoming ever more pressing, especially in settings where trust in central authorities is limitedβaddressing concerns that authoritarian regimes may use electoral systems to stay in power. The historical challenges of electoral fraud underscore the importance of exploring robust solutions. Modern cryptography provides powerful tools for building systems that can withstand manipulation and allow for public verification.
The goal of leveraging open technologies such as the Rust programming language and the Nostr protocol, along with cryptographic techniques (initially, blind signatures), to develop a fraud-resistant and publicly auditable voting system is recognized.
Derived from the initial consultation, the key security properties for this system are:
- Vote Secrecy/Anonymity: Voter choices must remain hidden from the Electoral Commission (EC) and third parties.
- Voter Authentication: Only eligible voters, identified by their Nostr public keys, are eligible to participate.
- Vote Uniqueness: Each voter may cast only one valid vote.
- Verifiability/Auditability: The electoral process and results must be publicly verifiable without compromising the identity of the voter, minimizing the trust required in the central tallying authority. (This central tallying authority may be comprised of a committee composed of a representative from each voting option.)
- Nostr's Role: Nostr is proposed as the underlying communication layer. Its decentralized, public/private event-based features can be used for both vote transmission and the implementation of a public bulletin board. Features such as NIP-59 Gift Wrap are used to encrypt data during transmission, protecting the confidentiality of the vote in transit.
Registered users with a Nostr key pair (public and private). The public key (voter_pk) identifies the voter to the Electoral Commission.
The EC is the central authority that manages elections and maintains voter anonymity through blind signatures:
- Multi-election support: Manages multiple concurrent elections via HashMap architecture
- Database persistence: SQLite database for elections, candidates, and voters
- Automatic status transitions: Elections progress from Open β InProgress β Finished automatically
- Blind signature voting: Issues anonymous voting tokens while preventing double voting
- Real-time tallying: Publishes vote counts to Nostr after each vote
- gRPC admin API: Complete election management interface
- Nostr integration: Publishes election announcements and results
- Open: Election created, voters can request tokens
- InProgress: Voting period active (automatically transitions based on start_time)
- Finished: Voting ended, final results published (based on end_time)
- Cancelled: Election cancelled via admin API
- elections.db: SQLite database with election, candidate, and voter data
- voters_pubkeys.json: Legacy voter authorization file (database takes precedence)
- app.log: Application logs with configurable verbosity
Nostr serves as the decentralized communication protocol for:
- Public election announcements (Kind 35000 addressable events)
- Real-time vote result publishing (Kind 35001 events)
- Encrypted voter-EC communication (NIP-59 Gift Wrap)
- Requesting blind signatures
- Casting encrypted votes
π For detailed Nostr protocol documentation, see NOSTR.md
Criptocracia is organized as a Cargo workspace containing two main binaries:
- ec: The Electoral Commission service that manages multiple elections, registers voters per election, issues blind signatures on voting tokens, receives anonymized votes, verifies them, and publishes results. Includes gRPC admin API.
- voter: The client-side application used by registered voters to request a blind-signed token, unblind it, and cast their vote.
Shared workspace dependencies include:
blind-rsa-signatures v0.15.2for RSA-based blind signature operations.nostr-sdk v0.41.0(featurenip59) for Gift Wrap message encryption and transport.base64,num-bigint-dig,sha2, and other utility crates for serialization and hashing.
-
Blind Signature Request
- Voter generates a random nonce (128-bit BigUint) and computes
h_n = SHA256(nonce). - Voter blinds
h_nusing the EC's RSA public key to obtainblinded_h_nand a blinding factor. blinded_h_nis Base64-encoded and sent to the EC via an encrypted Nostr Gift Wrap message.
- Voter generates a random nonce (128-bit BigUint) and computes
-
Blind Signature Issuance
- EC verifies the senderβs Nostr public key against the authorized voter list.
- EC uses its RSA secret key to sign
blinded_h_n, producing a blind signature. - EC encodes the blind signature in Base64 and returns it via Gift Wrap.
-
Unblinding and Voting
- Voter decodes the blind signature, unblinds it using the stored blinding factor, and verifies the resulting token against the ECβs public RSA key.
- The voter packages
(h_n, token, blinding_factor, candidate_id)into a colon-delimited payload, Base64-encodes the first three parts, and sends via Gift Wrap with a freshly generated Nostr key pair to anonymize origin.
-
Vote Reception and Tally
- EC receives the vote payload, decodes
h_n,token, andblinding_factorfrom Base64, and parsescandidate_id. - EC verifies the signature on
h_nand checks thath_nhas not been used before (prevents double voting). - Valid votes are recorded, tallied, and results published to Nostr as a custom event.
- EC receives the vote payload, decodes
NOSTR_PRIVATE_KEY: The EC's Nostr private key (hex format)
EC_PRIVATE_KEY: RSA private key content (PEM format)EC_PUBLIC_KEY: RSA public key content (PEM format)GRPC_BIND_IP: gRPC server bind address (default: 127.0.0.1)
- Environment variables (
EC_PRIVATE_KEY,EC_PUBLIC_KEY) - Files in current directory (
ec_private.pem,ec_public.pem)
# Voter's Nostr private key
nostr_private_key = "your_voter_private_key"
# EC's public key for verification
ec_public_key = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\n-----END PUBLIC KEY-----"
# Nostr relays for communication
relays = ["wss://relay.mostro.network"]The EC uses SQLite with the following tables:
- elections: Election metadata and status
- candidates: Candidate information per election
- voters: Authorized voters per election
- votes: Vote tracking and tallying
- Network Access: gRPC binds to localhost by default for security
- Key Management: Store RSA keys securely, never commit to version control
- Voter Privacy: Blind signatures ensure EC cannot correlate votes to voters
- Double Voting Prevention: Nonce tracking prevents multiple votes per voter
- Experimental: No formal security audit. Use only for study/demonstration.
- Single EC: Central authority issues tokensβno threshold or multi-party setup.
- Replay Protection: Based on one-time
h_n, but stronger measures (timestamps, channels) may be needed.
As mentioned above, both the voter and the EC communicate by sending Gift Wrap events, but there are other messages that the EC publishes.
An addressable event kind 35000 with the election information in a serialized json object in the content field of the event, here an example of the json object:
{
"id": "f5f7",
"name": "Libertad 2024",
"start_time": 1746611643,
"status": "open",
"candidates": [
{
"id": 1,
"name": "Donkey π«"
},
{
"id": 2,
"name": "Rat π"
},
{
"id": 3,
"name": "Sheep π"
},
{
"id": 4,
"name": "Sloth π¦₯"
}
],
"end_time": 1746615243,
"rsa_pub_key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyzrjKKlz8JpyKrqnCNr2n/iXwSgHAnrNyZwOJ6UW4actxDnI3dyweOqXtGZyIg4+PeEmDrTY5sP6pN2p5qVM6XGmt7DCfStJgaCpB0D/BZd/ar/sh9aj9ATLQe24/UDXweGTgzWVsky8uCRODczaxhDPXvwRAQICuZNO3OxQ5ss7uc1ZfSDS++857q8k6KHdbnWkAy3+NoGslZWqIQH/h9tDl8zfKH5AP5MZibdna+/P2wbz86/8uq+hBupxwympiQXxLB7rfjfOkLX22WguseovpbA/7If3LNned5UuxX1IxuFzBtw7W1RAy8B1MqlAobf5K+e4XzAzl49AqQn6swIDAQAB"
}The event would look like this:
[
"EVENT",
"7157aabf-389e-4d3e-9656-4d818159dff2",
{
"tags": [
[
"d",
"f5f7"
],
[
"expiration",
"1747043643"
]
],
"content": "{\"candidates\":[{\"id\":1,\"name\":\"Donkey π«\"},{\"id\":2,\"name\":\"Rat π\"},{\"id\":3,\"name\":\"Sheep π\"},{\"id\":4,\"name\":\"Sloth π¦₯\"}],\"end_time\":1746615243,\"id\":\"f5f7\",\"name\":\"Libertad 2024\",\"rsa_pub_key\":\"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyzrjKKlz8JpyKrqnCNr2n/iXwSgHAnrNyZwOJ6UW4actxDnI3dyweOqXtGZyIg4+PeEmDrTY5sP6pN2p5qVM6XGmt7DCfStJgaCpB0D/BZd/ar/sh9aj9ATLQe24/UDXweGTgzWVsky8uCRODczaxhDPXvwRAQICuZNO3OxQ5ss7uc1ZfSDS++857q8k6KHdbnWkAy3+NoGslZWqIQH/h9tDl8zfKH5AP5MZibdna+/P2wbz86/8uq+hBupxwympiQXxLB7rfjfOkLX22WguseovpbA/7If3LNned5UuxX1IxuFzBtw7W1RAy8B1MqlAobf5K+e4XzAzl49AqQn6swIDAQAB\",\"start_time\":1746611643,\"status\":\"open\"}",
"sig": "8b5bc04003c1d20ba98d33b2fd98a536d538d58afa1c9cfa81d3b693a3a20a764b51258e28335b10945439f7a09fca1d4d2ac40135a506e1bb4a8116259c46ab",
"id": "557d833876048e50068dfb06b82344a058d8104f08578e8060623ec8004c29ac",
"pubkey": "0000001ace57d0da17fc18562f4658ac6d093b2cc8bb7bd44853d0c196e24a9c",
"created_at": 1746611643,
"kind": 35000
}
]After each vote is received, the EC will publish another addressable event of kind 35001. The eventβs content field will contain the current status of the election as a serialized JSON array: the first element is the candidate ID, and the second element is the number of votes received. For example, in an election with the same candidates shown aboveβwhere Sloth π¦₯ received 21 vote and Sheep π received 35 votesβthe event would look like this:
[
"EVENT",
"7157aabf-389e-4d3e-9656-4d818159dff2",
{
"tags": [
[
"d",
"f5f7"
],
[
"expiration",
"1747043706"
]
],
"content": "[[4,21],[3,35]]",
"sig": "3eb717f176be137d7adc0f9e6d52556c38d988bce59c2f683cbdc6f796df3a3e6d31aecf2866fa2df5d58ce7a287236f83e2c368a89015f7b8f4c5eea21e134d",
"id": "7ae5c519f9e8886b70d0cef6155a69f3194e7b89cb88e589ed2012853915581e",
"pubkey": "0000001ace57d0da17fc18562f4658ac6d093b2cc8bb7bd44853d0c196e24a9c",
"created_at": 1746611706,
"kind": 35001
}
]This project is licensed under MIT. See LICENSE for details.
# Build both binaries in release mode
cargo build --release
# Run tests
cargo test
# Build for development
cargo build
# Run specific binary
cargo run --bin ec # Electoral Commission
cargo run --bin voter # Voter client
# Run gRPC client example
cargo run --example grpc_client --bin ec
# Code quality checks
cargo clippy
cargo fmt# Build Docker image
docker build -t criptocracia .
# Run with environment variables
docker run -e NOSTR_PRIVATE_KEY="your_key" -p 50001:50001 criptocracia-
"readonly database" error: Multiple EC processes running
pkill -f "target/release/ec" -
gRPC connection refused: Check if EC is running and port is accessible
netstat -tlnp | grep 50001 -
Voter authorization failed: Ensure voter is registered for the specific election
# Use gRPC API to add voter cargo run --example grpc_client --bin ec -
RSA key loading failed: Verify key files exist or environment variables are set
ls -la ec_*.pem echo $EC_PRIVATE_KEY | head -c 50
βββββββββββββββ gRPC βββββββββββββββββββ Nostr βββββββββββββββ
β Admin βββββββββββββΊβ EC βββββββββββββΊβ Voters β
β Client β Port 50001β (Election β Gift Wrap β (Client) β
β β β Commission) β Events β β
βββββββββββββββ βββββββββββββββββββ βββββββββββββββ
β
βΌ
βββββββββββββββββββ
β Database β
β (elections.db)β
β + Nostr Pub β
βββββββββββββββββββ
- Voter Registration: Admin adds voter pubkey to election via gRPC
- Token Request: Voter blinds nonce hash, sends via NIP-59 Gift Wrap
- Token Issuance: EC verifies voter authorization, issues blind signature
- Vote Casting: Voter unblinds token, sends vote with anonymous keypair
- Vote Verification: EC verifies token signature, prevents double voting
- Result Publishing: Real-time vote tallies published to Nostr
- Multi-election support with HashMap architecture
- gRPC admin API for election and voter management
- Database state restoration on startup
- Automatic election status transitions (Open β InProgress β Finished)
- Per-election voter authorization
- Automatic Nostr publishing for gRPC-created elections
- Cancel election functionality
- Blind signature voting protocol
- Real-time vote tallying and Nostr publishing
- TUI voter client with election selection
- Docker deployment support
- Registration token system for automated voter enrollment
- Voter self-registration with cryptographic proof
- Enhanced security auditing and logging
- Multi-EC threshold signatures
- Web-based voter interface
