Vada al contenuto
Condorcet.vote Alpha
← Documentazione tecnica

Blockchain

Tutto ciò di cui un verificatore indipendente ha bisogno per leggere, controllare e riconteggiare la catena di un’elezione, senza chiederci nulla.

Ogni elezione pubblica il proprio registro come una sequenza di file JSON, concatenati per hash e firmati dalla piattaforma. Questa pagina è il riferimento di quel formato: la busta comune, i quattro tipi di blocco campo per campo, le regole a livello di byte e gli schemi JSON con cui la piattaforma valida ogni blocco prima di sigillarlo, scaricabili qui sotto.

La versione è legata a ogni elezione per sempre e ripetuta in ogni blocco; una modifica del formato passerebbe per una nuova versione con il proprio insieme di schemi.

Protocollo v1

Download

Gli schemi JSON, così come sigillati.

JSON Schema draft 2020-12, un file per tipo di blocco più la busta della scheda firmata. Sono esattamente i file usati dalla piattaforma: nessuna proprietà aggiuntiva è mai accettata, e ciascuno è servito all’URL canonico scritto nel suo $id.

Busta

Cinque campi su ogni blocco.

Qualunque sia il tipo, un blocco si apre con la stessa busta, in questo ordine di lettura. L’hash e la firma del blocco non stanno mai dentro il JSON, perché un documento non può contenere il proprio digest: viaggiano accanto.

protocol_version intero
Sempre 1 per questa versione; identico su ogni blocco di una catena.
type stringa
Uno tra configuration, rings, votes, closure.
election_id UUID v4, minuscolo
L’identificativo dell’elezione, lo stesso su ogni blocco, e il parametro election_id di ogni firma ad anello.
block_number intero ≥ 0
Consecutivo per elezione, a partire da 0 per il blocco di configurazione.
previous_hash null o 128 caratteri esadecimali
SHA3-512 dei byte memorizzati del blocco precedente, esadecimale minuscolo; null solo sul blocco 0.
Busta · block.*.json
{
  "protocol_version": 1,
  "type": "configuration | rings | votes | closure",
  "election_id": "0c2e7e2a-5df3-4c0e-9d2b-3f6a8f0c9e21",
  "block_number": 0,
  "previous_hash": null,
  ...
}
Tipi di blocco

Quattro tipi, un ciclo fisso.

Un blocco di configurazione in testa, un blocco di anelli per gli anelli di lancio, un blocco di anelli per ogni anello di emergenza creato durante il voto, blocchi di voti a lotti, e un unico blocco di chiusura che termina la catena. Il risultato non è mai un blocco: si ricalcola dalle schede pubblicate.

Blocco 0 · genesi

configuration · la definizione congelata

Ciò che non cambierà più una volta aperto il voto. La presentazione estetica (logo, foto, messaggio di ringraziamento) è deliberatamente fuori dal blocco, non hashata.

title, description?, organizer_name stringhe
Contesto informativo, firmato con il blocco ma mai un input del conteggio.
election_type preferential | fptp
Scheda a graduatoria (metodi di Condorcet) o scelta singola. Il metodo di Condorcet stesso è pubblicato con il risultato, non qui.
seats intero ≥ 1
Seggi da assegnare.
voter_access_mode stringa
closed_nominative_list nella versione 1.
enrolment_mandatory booleano
Solo chiavi portate dagli elettori (BYO-only) se vero; chiavi generate dal server ammesse se falso.
non_anonymous_voting, nominative_participation, weighted_voting booleani
Le tre opzioni di privacy e ponderazione. non_anonymous_voting = true impone nominative_participation = true (imposto dallo schema).
max_ring_size intero ≥ 1
Il tetto della dimensione degli anelli di questa elezione: nessun anello lo supera, ogni anello di emergenza ha esattamente questa dimensione.
initial_electoral_roll_size intero ≥ 0
La dimensione della lista elettorale all’apertura. Gli elettori possono essere aggiunti durante il voto; la lista di chiusura dà la dimensione finale, quindi il numero di aggiunte è pubblico, mai chi.
ballot_rules oggetto
ties_allowed, partial_ranking_allowed, blank_allowed, full_ranking_required: i parametri della scheda da cui i vincoli di conteggio derivano in modo deterministico.
candidates[].label array di 2 a 30 oggetti
Etichette uniche, da 1 a 64 caratteri, ordinate per etichetta (ordine dei byte). L’etichetta è l’unico identificativo del candidato.
Blocco 1 · poi uno per anello di emergenza

rings · le chiavi autorizzate a firmare

L’elenco completo delle chiavi pubbliche di ogni anello. Il blocco 1 porta gli anelli di lancio; ogni blocco di anelli successivo porta un anello di emergenza, deducibile dalla posizione. L’insieme dei membri di un anello è congelato per sempre una volta pubblicato.

rings[].ring_id UUID
L’identificativo dell’anello referenziato da ogni scheda firmata contro di esso; anelli ordinati per ring_id.
rings[].ring_digest ring_<64 hex>_<8 hex>
L'impronta cryptoVote canonica dell'insieme dei membri dell'anello, indipendente dall'ordine. Derivabile da public_keys: un verificatore la ricalcola; è pubblicata perché un elettore possa confrontarla con quella calcolata dal suo browser dall'anello ricevuto.
rings[].public_keys[] pk_<64 hex>_<8 hex>
Chiavi pubbliche prefissate con checksum, ordinate in modo crescente, uniche; al massimo max_ring_size per anello.

Gli anelli di lancio condividono una dimensione unica per elezione (tra il minimo di 100 chiavi e il tetto); gli anelli di emergenza hanno esattamente il tetto; gli anelli di un’elezione BYO-only contengono gli iscritti, ripartiti equamente.

Blocchi successivi · a lotti

votes · le schede firmate

Le schede sono pubblicate a lotti di almeno 600 (il resto alla chiusura), mai una per una, e ordinate per immagine di chiave in ogni blocco. Nessun timestamp, nessun ordine di arrivo.

ballots[].ring_id UUID
L’anello con le cui chiavi pubbliche si verifica la firma.
ballots[].key_image ki_<64 hex>_<8 hex>
L’immagine di chiave legata all’elezione: unica per elezione, e una seconda scheda con la stessa immagine è un doppio voto, pubblicamente evidente. Chiave di ordinamento del blocco.
ballots[].signature blsag_…
La firma ad anello collegabile (variante bLSAG su Ristretto255) sui byte esatti del payload.
ballots[].payload stringa (JSON opaco)
Il documento firmato, verbatim: {"v":1,"election":"…","ballot":"A>B=C","weight":1[,"voter":"…"]}. Mai riserializzato (si veda lo schema del payload).

Il campo ballot è una stringa piatta in sintassi di graduatoria CEF (o la sentinella /EMPTY_RANKING/). Il suo contenuto non è deliberatamente validato: un’espressione malformata è classificata scheda nulla al conteggio secondo una regola pubblicata e riproducibile.

Blocco finale · terminatore

closure · totali e registro delle presenze

Esattamente uno, l’ultimo, nulla dopo di lui. Possedere il blocco di chiusura firmato è la prova che una catena è completa. Il registro delle presenze vi appare una sola volta, mai durante il voto.

totals oggetto di interi
block_count, ballot_count, ring_count, voted_count, voted_weight, verificabili in modo incrociato con la catena stessa.
electoral_roll[].voter_id UUID v4
L’identificativo elettore pubblicato (uno pseudonimo casuale); la lista alla chiusura, ordinata per voter_id, una riga per elettore, nessuna contrassegnata come aggiunta.
electoral_roll[].key_provisioning_mode stringa
server_generated, organizer_imported o voter_enrolled: se la piattaforma abbia mai detenuto la chiave di questo elettore è un fatto pubblico.
electoral_roll[].ring_id UUID o null
L’anello in cui l’elettore ha votato; null se non ha votato. Il legame verificabile elettore → anello.
electoral_roll[].distribution_count intero ≥ 0
Quante chiavi sono state distribuite all’elettore (al massimo 3 per default; le estensioni concesse dall’organizzatore restano visibili).
electoral_roll[].has_voted, weight booleano, intero ≥ 1
Flag di partecipazione e peso della scheda autorevole (1 salvo weighted_voting).
electoral_roll[].voter_name, email_digest stringhe, opzionali
Solo con divulgazione nominativa, sugli elettori che hanno votato: nome in chiaro e SHA3-256(election_id ‖ e-mail normalizzata).
Serializzazione

I byte sono il contratto.

  • Memorizzato = servito = firmato. Il file del blocco è scritto una volta e mai riserializzato. Non esiste un passaggio di JSON canonico: ciò che è hashato e firmato è il file così come servito.
  • JSON UTF-8, un elemento per riga. L’oggetto esterno è indentato; ogni elemento delle grandi collezioni (candidates, rings, ballots, electoral_roll) è un oggetto compatto sulla propria riga. Barre e Unicode non sono escapati.
  • Ordinamento deterministico, ordine dei byte. candidates per label, public_keys crescente, ballots per key_image, electoral_roll per voter_id, rings per ring_id. Confronto grezzo dei byte, mai una collazione linguistica.
  • Solo interi, nessun timestamp. protocol_version, block_number, seats, dimensioni e totali sono interi non negativi; ogni altro valore è una stringa. Nessun blocco porta una data, nessuna scheda un ordine di arrivo.
  • Nessuna proprietà aggiuntiva, nessuna chiave duplicata. Ogni schema vieta i campi sconosciuti; un verificatore deve rifiutare un blocco il cui JSON ripete una chiave, poiché i parser divergono su quale valore prevalga.
Integrità

Hash, firma, timestamp.

  • Concatenamento SHA3-512. Il digest di un blocco è lo SHA3-512 dei suoi byte memorizzati, 128 caratteri esadecimali minuscoli; è il previous_hash del blocco successivo. Lo si ricalcola con openssl dgst -sha3-512.
  • Firma OpenPGP v6 staccata. Ogni blocco è firmato sui suoi byte esatti dalla chiave Ed25519 della piattaforma (RFC 9580), come firma staccata armored nel file .sig. La sua data di creazione è il signed_at del blocco; una notazione domain@condorcet.vote = chain-block/v1 impedisce il replay su un’altra classe di artefatti.
  • La rotazione delle chiavi resta verificabile. I certificati ritirati restano pubblicati con la loro scadenza: le regole di validità OpenPGP standard accettano i blocchi firmati nella finestra di una chiave e rifiutano gli altri.
  • Ancoraggio OpenTimestamps. Lo SHA-256 semplice di ogni blocco è impegnato nella blockchain Bitcoin; il file .ots è servito in attesa o completato e si verifica offline con gli strumenti standard.
  • File autoidentificanti. Gli artefatti memorizzati portano l’identificativo dell’elezione ({election}_{n}.json e file compagni), così un file replicato resta identificabile una volta staccato dal suo URL.
La marcatura temporale OpenTimestamps nel dettaglio
Endpoint

File semplici, nessuna API.

Tutto è servito come file memorizzabili in cache e replicabili sul dominio principale, dall’apertura del voto fino all’archiviazione dell’elezione. Un’elezione annullata ritira il proprio registro.

/elections/{election}/chain
Il manifesto: elenco ordinato dei blocchi con tipo, hash, signed_at, URL degli artefatti e stato del timestamp.
/elections/{election}/chain/{n}.json
Il blocco n, i byte memorizzati esatti.
/elections/{election}/chain/{n}.json.sig
La firma OpenPGP staccata armored del blocco n.
/elections/{election}/chain/{n}.json.ots
La prova OpenTimestamps del blocco n, in attesa o completata.
/.well-known/blockchain-signing-key
I certificati di firma della piattaforma, attivi e ritirati, concatenati (application/pgp-keys).
/schemas/blockchain/block.{type}.schema.json
Gli schemi JSON qui sopra, ai loro URL canonici $id.
La procedura di verifica completa, controllo per controllo