Presentamos Achronyme — un lenguaje para pruebas zero-knowledge. Lee el anuncio

Votación Secreta

Construye un circuito de votación privada con elegibilidad de votantes, privacidad del voto y prevención de doble voto.

La votación secreta es una de las aplicaciones más naturales de las pruebas de conocimiento cero. Un votante demuestra que es elegible y emite un voto válido — sin revelar su identidad ni qué opción eligió. Un nulificador público evita que el mismo votante vote dos veces.

Conceptos

El circuito combina cuatro componentes:

  • Compromiso del votante — cada votante registra un compromiso poseidon(secret, 0) derivado de un secreto privado. El compromiso es público; el secreto no.
  • Membresía Merkle — todos los compromisos de votantes son hojas en un árbol Merkle. El votante demuestra que su compromiso está en el árbol sin revelar cuál hoja es.
  • Nulificadorposeidon(secret, election_id) produce un valor único y determinista vinculado tanto al votante como a la elección. Publicar el nulificador permite detectar duplicados sin saber quién votó.
  • Validez del votorange_check(vote, 1) asegura que el voto sea 0 o 1.

Definición del Circuito

Crea secret_vote.ach:

circuit secret_vote(merkle_root: Public, nullifier: Public, vote: Public, election_id: Public, secret: Witness, path: Witness Field[2], indices: Witness Bool[2]) {
    // 1. Compromiso del votante (oculto — calculado desde el secreto privado)
    let commitment = poseidon(secret, 0)

    // 2. Membresía Merkle: el votante está en el árbol de votantes registrados
    merkle_verify(merkle_root, commitment, path, indices)

    // 3. Correctitud del nulificador: previene doble voto
    let expected_nullifier = poseidon(secret, election_id)
    assert_eq(expected_nullifier, nullifier)

    // 4. Validez del voto: debe ser 0 o 1
    range_check(vote, 1)
}

El compromiso se calcula dentro del circuito a partir del testigo secret. Nunca aparece como entrada pública — el verificador solo ve la raíz Merkle, el nulificador, el voto y el ID de la elección.

Construir el Registro de Votantes

Antes de ejecutar el circuito, necesitas un árbol Merkle de compromisos. Aquí un árbol de 4 votantes (profundidad 2) construido en modo VM:

// Cuatro secretos de votantes
let s0 = 0p42
let s1 = 0p111
let s2 = 0p222
let s3 = 0p333

// Compromisos
let c0 = poseidon(s0, 0)
let c1 = poseidon(s1, 0)
let c2 = poseidon(s2, 0)
let c3 = poseidon(s3, 0)

// Nivel 0: hashes de pares
let n0 = poseidon(c0, c1)
let n1 = poseidon(c2, c3)

// Nivel 1: raíz
let root = poseidon(n0, n1)
print(root)

Al ejecutar con ach run se obtiene la raíz:

ach run registry.ach
# Field(16562627490493722277540343453474560507943355785745140792129356826951042972366)

Calcular la Ruta de Prueba

El votante 0 (secreto 42) está en el índice 0 del árbol (binario 00). Su prueba Merkle contiene el hermano en cada nivel:

  • Nivel 0: c0 es el hijo izquierdo, el hermano es c1 (compromiso del votante 1). Índice = 0.
  • Nivel 1: n0 es el hijo izquierdo, el hermano es n1. Índice = 0.

Entonces path = [c1, n1] e indices = [0, 0].

Compilar el Circuito

ach circuit secret_vote.ach \
    --inputs "merkle_root=16562627490493722277540343453474560507943355785745140792129356826951042972366,\
nullifier=4027913667401648903638418705764660665764112454358309045410324429160920395813,\
vote=1,election_id=1001,secret=42,\
path_0=6742193431752037917634653485837689273334250178444557194345979079134234961755,\
path_1=2479855382401079998356559563096754868958560665915964078751529288374953894653,\
indices_0=0,indices_1=0"

Costo de Restricciones

ComponenteRestricciones
poseidon(secret, 0) — compromiso361
merkle_verify (profundidad 2)~726
poseidon(secret, election_id) — nulificador361
assert_eq — verificación del nulificador1
range_check(vote, 1) — validez del voto2
Total~1,451

Con profundidad 20 (~1M votantes), el total sube a aproximadamente 8,100 restricciones — todavía muy rápido de probar.

Usando prove {} en su Lugar

Puedes generar la prueba en línea usando un bloque prove {}. El ámbito externo calcula el árbol Merkle en modo VM, y el bloque prove genera la prueba ZK:

let secret: Field = 0p42
let election_id: Field = 0p1001
let vote: Field = 0p1

// Construir registro de votantes
let commitment: Field = poseidon(secret, 0)
let voter1: Field = poseidon(0p111, 0p0)
let voter2: Field = poseidon(0p222, 0p0)
let voter3: Field = poseidon(0p333, 0p0)

let n0: Field = poseidon(commitment, voter1)
let n1: Field = poseidon(voter2, voter3)
let merkle_root: Field = poseidon(n0, n1)

let nullifier: Field = poseidon(secret, election_id)

// Prueba Merkle para votante 0
let path_0: Field = voter1
let path_1: Field = n1
let indices_0: Field = 0p0
let indices_1: Field = 0p0

prove(merkle_root: Public, nullifier: Public, vote: Public, election_id: Public) {
    let commitment: Field = poseidon(secret, 0)
    merkle_verify(merkle_root, commitment, path, indices)

    let expected_nullifier: Field = poseidon(secret, election_id)
    assert_eq(expected_nullifier, nullifier)

    range_check(vote, 1)
}

Ejecuta con ach run secret_vote.ach. El bloque prove {} compila el circuito, genera el testigo, verifica las restricciones y produce una prueba — todo en un solo paso.

Escalar

  • Más votantes: aumenta la profundidad del árbol Merkle. Profundidad 20 soporta ~1M votantes con ~8,100 restricciones.
  • Múltiples candidatos: reemplaza range_check(vote, 1) con range_check(vote, N) donde N = ceil(log2(candidatos)).
  • Conteo: publica todos los pares (nulificador, voto). Cualquiera puede verificar que no hay nulificadores duplicados y sumar los votos, sin saber quién emitió cada voto.

Aplicaciones

  • Gobernanza DAO — los poseedores de tokens votan sin revelar su posición
  • Encuestas anónimas — recopilar retroalimentación honesta con unicidad garantizada
  • Elecciones on-chain — enviar pruebas a un verificador Solidity (ach circuit --solidity)
Navigation