DEV Community

Cover image for PAN-RAP Municipio v0.5: Anonymous Voting with Blind Schnorr, HMAC Eligibility & PDF Minutes
Fenix
Fenix

Posted on

PAN-RAP Municipio v0.5: Anonymous Voting with Blind Schnorr, HMAC Eligibility & PDF Minutes

PAN-RAP Municipio v0.5

Anonymous voting system with cryptographic integrity for municipal civic governance. Blind Schnorr over secp256k1, modular HMAC eligibility, convocatoria lifecycle, and verifiable PDF minutes generation.

What is it

PAN-RAP (Planet Assembly Network / Red Asamblearia Planetaria) is a decentralized civic governance ecosystem designed for municipalities of ~25,000 inhabitants. This repo is the reference implementation of the anonymous voting module.

Stack:

  • Backend: Python 3.12+ / FastAPI / coincurve (libsecp256k1)
  • Frontend: JavaScript ES modules / @noble/curves vendored (no CDN)
  • Crypto: Blind Schnorr over secp256k1 (65-byte token)
  • DB: SQLite (WITHOUT ROWID on ballot box)
  • License: AGPL-3.0-or-later

What it delivers (Phase 5.5 completed)

Feature Status Detail
Blind Schnorr Engine ✅ Issuer (coincurve) + Ballot Box (fail-closed verification) + Frontend (@noble/curves)
Modular Eligibility ✅ 4 modes: census (HMAC), in-person (single-use codes), endorsement (K endorsements), eid (placeholder)
Convocatoria Lifecycle ✅ Validated transitions: draft → scheduled → emission → voting → closed → scrutinized → archived
Public Scrutiny ✅ GET /convocatorias/{id}/escrutinio with tally, turnout, ballot box hash
Full Ballot Box Download ✅ GET /convocatorias/{id}/urna votes ordered by r_prima
PDF Minutes ✅ POST /convocatorias/{id}/acta with reportlab, only in scrutinized state
Independent Verifier ✅ scripts/verificador_independiente.py audits complete ballot box
Tests ✅ 10 passed (3 eligibility + 7 lifecycle)

What it does NOT do yet

Phase Status Pending
Phase 6 ⏳ External cryptographic audit (Cure53, Trail of Bits, etc.)
Phase 7 ⏳ Real municipality pilot deployment
Memory state persistence ⏳ In-person codes and endorsements lost on server restart
List convocatorias endpoint ⏳ Public GET /convocatorias for discovery
Full eid mode ⏳ OIDC / national digital identity integration

Transparency note: The project is in Phase 5.5. Tests pass (10/10), the crypto engine is internally audited, but no external audit has been performed. Not suitable for binding use until Phase 6 completes.

How to try it

# 1. Clone and setup
git clone https://github.com/amurlaniakea/pan-rap-municipio.git
cd pan-rap-municipio
python3.12 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt  # includes reportlab for PDF minutes

# 2. Run tests
pytest tests/ -v
# → 10 passed

# 3. Start server
export PANRAP_EMISOR_KEY_HEX="$(openssl rand -hex 32)"
fastapi dev main.py

# 4. Quick flow (census mode example)
curl -X POST http://localhost:8000/convocatorias \
  -H "Content-Type: application/json" \
  -d '{"titulo":"Test","texto":"...","opciones":["a_favor","en_contra"],"modo_elegibilidad":"padron","nivel_garantia":"alta","emision_abre_en":"2026-11-01T00:00:00","voto_abre_en":"2026-11-02T00:00:00","cierra_en":"2026-11-03T00:00:00"}'
# → {"id": "uuid", "estado": "borrador"}

curl -X POST http://localhost:8000/convocatorias/{id}/censo \
  -H "Content-Type: application/json" \
  -d '{"identificadores": ["dni_123"]}'

curl -X PATCH http://localhost:8000/convocatorias/{id}/estado \
  -H "Content-Type: application/json" \
  -d '{"estado": "programada"}'

curl -X PATCH http://localhost:8000/convocatorias/{id}/estado \
  -H "Content-Type: application/json" \
  -d '{"estado": "emision"}'

# 5. Verify ballot box and generate minutes
python scripts/verificador_independiente.py
# → VERIFICATION SUCCESSFUL: X valid votes, 0 invalid
Enter fullscreen mode Exit fullscreen mode

Security Architecture (summary)

  • S1 Sequential emission: threading.Lock() + TTL 10s
  • S4 Zero int arithmetic on secrets: only coincurve
  • S5 Ephemeral k per session: PrivateKey() new each inicio()
  • S6 Fail-closed validation: urna_schnorr.verificar() catches exceptions
  • S7 Key outside git: .gitignore covers clave_emisor.hex
  • S8 Vendored client: frontend/lib/ with @noble/curves (no CDN)
  • Eligibility: persona_ref = HMAC-SHA256(secreto_convocatoria, convocatoria:identificador) — different per convocatoria, non-linkable
  • Persistent secret: stored in convocatorias.secreto_hmac, never exposed in HTTP responses

References


Author: Pedro Sordo Martínez

License: AGPL-3.0-or-later

Top comments (0)