[ actualmente en mantenimiento ]
← Back

Blockchain Aplicado Al Voto Digital (Cámara de Diputados)

Prototipo de sistema de voto digital basado en blockchain, desarrollado y premiado en un hackathon realizado en la Cámara de Diputados.

June 20248 min read
Blockchain • Gobernanza • Voto Digital • zK • Smart Contracts • Substrate

Problem

Los sistemas de votación tradicionales enfrentan desafíos críticos en términos de transparencia, confianza y verificabilidad. En contextos políticos y gubernamentales, cualquier falla —técnica o perceptual— puede comprometer la legitimidad de los resultados y la confianza ciudadana.

Además, muchas propuestas de voto digital no logran equilibrar tres factores clave: seguridad, anonimato y auditoría pública, especialmente en entornos políticamente sensibles.

Solution

Se diseñó un prototipo de voto digital descentralizado basado en una arquitectura conceptual que integra blockchain, Zero-Knowledge Proofs (zk) y Smart Contracts, garantizando:

  • Inmutabilidad de los votos emitidos
  • Anonimato del votante
  • Trazabilidad y auditoría pública de los resultados
  • Reducción de intermediarios y puntos únicos de falla
  • Portabilidad entre diferentes ecosistemas blockchain

El proyecto fue desarrollado y presentado durante un hackathon realizado en la Cámara de Diputados, donde resultó ganador tras más de 30 horas continuas de trabajo, validando tanto su solidez técnica como su viabilidad institucional.

Modelo Conceptual: Separación de Capas

La premisa central del sistema es separar de forma estricta la identidad del votante, la emisión del voto y la gobernanza del proceso, logrando un equilibrio entre seguridad, anonimato y auditabilidad pública.

En este modelo:

  • La blockchain no "conoce" a las personas, solo verifica reglas
  • Zero-Knowledge Proofs permiten demostrar legitimidad sin exponer información sensible
  • Los Smart Contracts actúan como árbitros neutrales del proceso
  • La arquitectura es agnóstica a la blockchain, permitiendo implementaciones en EVM y Substrate

Technical Approach

1. Identidad y Elegibilidad (Capa zk – Off-Chain)

Toda la información sensible ocurre fuera de la blockchain. Antes de emitir un voto, el ciudadano genera una prueba criptográfica Zero-Knowledge que demuestra que:

  • Pertenece al padrón electoral
  • Está habilitado para votar
  • No ha votado previamente

Esta prueba se construye contra un conjunto autorizado (por ejemplo, un Merkle Tree del padrón), pero sin revelar identidad, nombre, dirección blockchain ni datos personales. El resultado es una afirmación verificable del tipo: "Cumplo las reglas del sistema" sin decir quién las cumple.

Aquí, la confianza no se deposita en una autoridad central, sino en la validez matemática de la prueba.

2. Emisión del Voto (Capa Blockchain – Smart Contract)

El smart contract VotingSystem actúa como el árbitro neutral del proceso electoral. Su responsabilidad no es identificar votantes, sino ejecutar reglas inmutables.

La arquitectura fue implementada en dos ecosistemas blockchain diferentes, demostrando la portabilidad del diseño:

Implementación EVM (Solidity)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract VotingSystem {
    // Estructura del voto
    struct Vote {
        uint256 folio;           // Identificador único institucional
        uint256 candidateId;     // ID del candidato seleccionado
        bool isCoalition;        // Indica si es voto en coalición
        address voter;           // Dirección técnica (no identidad civil)
        uint256 timestamp;       // Momento de emisión
    }

    // Estado del sistema
    mapping(uint256 => Vote) public votes;              // Registro de votos
    mapping(uint256 => bool) public usedFolios;         // Control de folios utilizados
    mapping(uint256 => uint256) public voteCounts;      // Conteo por candidato
    mapping(address => bool) public validators;         // Validadores autorizados
    
    uint256 public totalVotes;                          // Total de votos emitidos
    bool public votingOpen;                             // Estado de la votación
    uint256 public validatorCount;                      // Cantidad de validadores
    uint256 public closeConfirmations;                  // Confirmaciones para cierre

    address public admin;

    // Eventos públicos e inmutables
    event VoteCast(uint256 indexed folio, uint256 candidateId, bool isCoalition);
    event VotingClosed(uint256 totalVotes);
    event ValidatorAdded(address validator);

    modifier onlyAdmin() {
        require(msg.sender == admin, "Solo el administrador");
        _;
    }

    modifier onlyValidator() {
        require(validators[msg.sender], "Solo validadores");
        _;
    }

    modifier votingIsOpen() {
        require(votingOpen, "Votacion cerrada");
        _;
    }

    constructor() {
        admin = msg.sender;
        votingOpen = true;
    }

    function addValidator(address _validator) external onlyAdmin {
        require(!validators[_validator], "Ya es validador");
        validators[_validator] = true;
        validatorCount++;
        emit ValidatorAdded(_validator);
    }

    function castVote(
        uint256 _folio,
        uint256 _candidateId,
        bool _isCoalition
    ) external votingIsOpen {
        require(!usedFolios[_folio], "Folio ya utilizado");
        require(_candidateId > 0, "Candidato invalido");

        votes[totalVotes] = Vote({
            folio: _folio,
            candidateId: _candidateId,
            isCoalition: _isCoalition,
            voter: msg.sender,
            timestamp: block.timestamp
        });

        usedFolios[_folio] = true;
        voteCounts[_candidateId]++;
        totalVotes++;

        emit VoteCast(_folio, _candidateId, _isCoalition);
    }

    function confirmClose() external onlyValidator {
        require(votingOpen, "Ya esta cerrada");
        closeConfirmations++;

        if (closeConfirmations >= validatorCount) {
            votingOpen = false;
            emit VotingClosed(totalVotes);
        }
    }

    function getResults(uint256 _candidateId) external view returns (uint256) {
        return voteCounts[_candidateId];
    }

    function isFolioUsed(uint256 _folio) external view returns (bool) {
        return usedFolios[_folio];
    }
}

Implementación Substrate (Rust)

use frame_support::{
    decl_module, decl_storage, decl_event, decl_error, dispatch,
    traits::Get,
};
use frame_system::ensure_signed;
use sp_std::prelude::*;

#[cfg(feature = "std")]
use serde::{Deserialize, Serialize};

pub trait Config: frame_system::Config {
    type Event: From<Event<Self>> + Into<<Self as frame_system::Config>::Event>;
}

// Estructura equivalente a la versión Solidity
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub struct Vote<AccountId> {
    folio: u32,
    candidate_id: u32,
    voter: AccountId,
    is_coalition: bool,
}

// Storage: equivalente a los mappings de Solidity
decl_storage! {
    trait Store for Module<T: Config> as VotingSystem {
        // Control de folios utilizados
        FolioUsed get(fn folio_used): map hasher(blake2_128_concat) u32 => bool;
        
        // Registro de votos por folio
        Votes get(fn votes): map hasher(blake2_128_concat) u32 => Vote<T::AccountId>;
        
        // Control de votantes (dirección ya votó)
        HasVoted get(fn has_voted): map hasher(blake2_128_concat) T::AccountId => bool;
        
        // Lista de validadores autorizados
        Validators get(fn validators): Vec<T::AccountId>;
        
        // Contador de confirmaciones para cierre
        ValidatorCount get(fn validator_count): u32;
        
        // Estado de la votación
        VotingClosed get(fn voting_closed): bool;
        
        // Tiempo de inicio (adicional en Substrate)
        VotingStartTime get(fn voting_start_time): T::BlockNumber;
    }
}

// Eventos equivalentes a los de Solidity
decl_event!(
    pub enum Event<T> where AccountId = <T as frame_system::Config>::AccountId {
        VoteCast(u32, u32, AccountId, bool),
        VotingClosed(),
    }
);

// Manejo de errores tipado
decl_error! {
    pub enum Error for Module<T: Config> {
        NotValidator,
        VotingIsClosed,
        FolioAlreadyUsed,
        AlreadyVoted,
        InvalidCandidateId,
    }
}

// Funciones extrinsics (equivalente a funciones públicas)
decl_module! {
    pub struct Module<T: Config> for enum Call where origin: T::Origin {
        type Error = Error<T>;
        fn deposit_event() = default;

        // Emitir voto (equivalente a castVote en Solidity)
        #[weight = 10_000]
        pub fn cast_vote(
            origin, 
            folio: u32, 
            candidate_id: u32, 
            is_coalition: bool
        ) -> dispatch::DispatchResult {
            let sender = ensure_signed(origin)?;

            // Validaciones equivalentes a los require() de Solidity
            ensure!(!Self::voting_closed(), Error::<T>::VotingIsClosed);
            ensure!(!Self::folio_used(folio), Error::<T>::FolioAlreadyUsed);
            ensure!(!Self::has_voted(&sender), Error::<T>::AlreadyVoted);
            ensure!(candidate_id > 0, Error::<T>::InvalidCandidateId);

            // Crear estructura de voto
            let vote = Vote {
                folio,
                candidate_id,
                voter: sender.clone(),
                is_coalition,
            };

            // Actualizar estado
            <FolioUsed>::insert(folio, true);
            <Votes<T>>::insert(folio, vote);
            <HasVoted<T>>::insert(&sender, true);

            // Emitir evento
            Self::deposit_event(RawEvent::VoteCast(folio, candidate_id, sender, is_coalition));

            Ok(())
        }

        // Cerrar votación con gobernanza distribuida
        #[weight = 10_000]
        pub fn close_voting(origin) -> dispatch::DispatchResult {
            let sender = ensure_signed(origin)?;
            
            // Solo validadores pueden confirmar cierre
            ensure!(Self::is_validator(&sender), Error::<T>::NotValidator);

            // Incrementar contador de confirmaciones
            let count = Self::validator_count().saturating_add(1);
            ValidatorCount::put(count);

            // Cerrar solo cuando todos los validadores confirman
            if count == Self::validators().len() as u32 {
                VotingClosed::put(true);
                Self::deposit_event(RawEvent::VotingClosed());
            }

            Ok(())
        }
    }
}

// Funciones auxiliares
impl<T: Config> Module<T> {
    fn is_validator(address: &T::AccountId) -> bool {
        Self::validators().contains(address)
    }
}

Comparación de Implementaciones

| Aspecto           | Solidity (EVM)                 | Rust (Substrate)             |
| ----------------- | ------------------------------ | ---------------------------- |
|   Ecosistema      | Ethereum, Polygon, BSC         | Polkadot, Kusama, parachains |
|   Tipo de dato    | `address` (identidad on-chain) | `AccountId` (tipo genérico)  |
|   Storage         | `mapping` (clave → valor)      | `StorageMap` (con hasher)    |
|   Validación      | `require()` (condición)        | `ensure!()` (macro)          |
|   Seguridad       | Gas, guards reentrancy         | Weight, tipado fuerte        |
|   Eventos         | `emit Event()`                 | `deposit_event()`            |
|   Modificadores   | `modifier` (pre/post lógica)   | Lógica directa en función    |
|   Errores         | `revert` (string)              | `enum` tipado                |

Ventajas de la implementación en Rust/Substrate:

  1. Seguridad de tipos: El compilador de Rust previene clases enteras de bugs
  2. Sistema de pesos: Control más granular de recursos computacionales
  3. Errores tipados: Mejor manejo y debugging de errores
  4. Parachain-ready: Compatible con ecosistema Polkadot
  5. Sin gas variable: Costos de transacción más predecibles

Ventajas de la implementación en Solidity/EVM:

  1. Ecosistema maduro: Más herramientas y auditorías disponibles
  2. Mayor adopción: Más desarrolladores familiarizados con EVM
  3. Interoperabilidad: Compatible con múltiples cadenas EVM
  4. Tooling establecido: Hardhat, Truffle, OpenZeppelin

Análisis Arquitectónico Común

Ambas implementaciones respetan los mismos principios fundamentales:

Estructura de Datos Mínima:

// Solidity
struct Vote {
    uint256 folio;
    uint256 candidateId;
    bool isCoalition;
    address voter;
    uint256 timestamp;
}
// Rust
pub struct Vote<AccountId> {
    folio: u32,
    candidate_id: u32,
    voter: AccountId,
    is_coalition: bool,
}

Restricciones Inmutables:

Ambas versiones implementan las mismas validaciones críticas:

  • Un folio no puede reutilizarse
  • Un votante no puede votar dos veces
  • La votación debe estar abierta
  • El candidato debe ser válido

Gobernanza Distribuida:

Ambas implementan el mismo mecanismo de cierre multi-firma:

  • Se requiere confirmación de todos los validadores
  • Ningún actor individual puede cerrar unilateralmente
  • El proceso es transparente y auditable

3. El Folio como Puente Institucional

El uso de un folio cumple un rol clave para contextos gubernamentales y electorales reales:

  • Puede existir previamente en sistemas físicos o digitales
  • Puede ser validado dentro de la prueba zk
  • Permite trazabilidad institucional sin comprometer anonimato

Flujo completo con zk (agnóstico a la blockchain):

  1. El ciudadano presenta su folio + credencial electoral
  2. Sistema off-chain genera prueba zk que valida:
    • Folio pertenece al padrón
    • Ciudadano está habilitado
    • Folio no ha sido usado
  3. Se genera un nullifier criptográfico (hash irreversible)
  4. El contrato (EVM o Substrate) verifica el nullifier
  5. Voto se registra sin revelar identidad

Esto conecta el sistema descentralizado con procesos electorales tradicionales, facilitando adopción, auditoría y confianza pública.

4. Portabilidad y Escalabilidad

La existencia de ambas implementaciones demuestra que la arquitectura es blockchain-agnostic:

  • EVM: Ideal para despliegue rápido en ecosistemas maduros
  • Substrate: Ideal para blockchains específicas de propósito gubernamental

Esta portabilidad permite:

  • Evaluación comparativa de costos y rendimiento
  • Migración entre ecosistemas según necesidades institucionales
  • Resiliencia ante cambios tecnológicos

Síntesis del Modelo

  • Zero-Knowledge protege al votante (identidad off-chain)
  • Blockchain protege al voto (inmutabilidad)
  • El Smart Contract protege las reglas (ejecución automática)
  • Los validadores protegen el proceso (gobernanza distribuida)
  • La portabilidad protege contra obsolescencia tecnológica

El sistema no necesita confiar en personas, instituciones o intermediarios. Confía en criptografía, consenso y código inmutable. La legitimidad del voto no se declara: se demuestra criptográficamente.

Results

  • Proyecto ganador en hackathon realizado en la Cámara de Diputados
  • Validación del enfoque técnico en un entorno políticamente sensible
  • Demostración funcional de voto inmutable y auditable
  • Reconocimiento por el equilibrio entre seguridad, anonimato y transparencia
  • Doble implementación (EVM + Substrate) demostrando portabilidad arquitectónica
  • Smart Contracts desplegados y testeados durante las 30+ horas del evento
  • Evaluación positiva sobre aspectos legales y vulnerabilidades físicas

Tech Stack

Blockchain Platforms

  • EVM: Ethereum / Polygon / BSC compatible
  • Substrate: Polkadot ecosystem ready

Smart Contracts

  • Solidity: ^0.8.0 (EVM implementation)
  • Rust: frame_support + sp_std (Substrate pallet)

Security & Privacy

  • Zero-Knowledge Proofs: zk-SNARKs (conceptual layer)
  • Cryptographic Primitives: Blake2, nullifiers, Merkle Trees

Architecture

  • Layered Design: Off-chain identity + on-chain voting
  • Multi-validator Governance: Distributed consensus mechanism
  • Cross-chain Compatible: Blockchain-agnostic logic

Development

  • Methodology: Rapid prototyping with production-grade security
  • Focus: Social impact + institutional viability

Key Takeaways

La experiencia de más de 30 horas continuas no solo validó la arquitectura técnica, sino que expuso la complejidad de diseñar sistemas de votación que deben cumplir con:

  • Requisitos legales (validez jurídica del voto electrónico)
  • Seguridad física (protección de dispositivos y infraestructura)
  • Confianza social (aceptación ciudadana e institucional)
  • Escalabilidad técnica (procesamiento de millones de votos)
  • Portabilidad tecnológica (independencia de plataformas específicas)

La doble implementación en Solidity y Rust demuestra que los principios arquitectónicos trascienden tecnologías específicas. El desafío real no es solo escribir código correcto, sino diseñar sistemas que la sociedad pueda confiar, independientemente de la blockchain subyacente.

Lecciones de Portabilidad

  • El diseño conceptual sólido permite implementaciones en múltiples ecosistemas
  • La separación de capas (zk off-chain + smart contract on-chain) es universal
  • Los principios de gobernanza distribuida son independientes de la plataforma
  • La arquitectura debe priorizar conceptos sobre tecnologías específicas
Blockchain Aplicado Al Voto Digital (Cámara de Diputados) · Irwing Duran