mirror of
https://github.com/saymrwulf/swisspost-evoting-go-poc.git
synced 2026-05-14 20:58:03 +00:00
Proof-of-concept reimplementation of the Swiss Post e-voting cryptographic protocol in Go. Single binary, 52 source files, 2 dependencies. Covers ElGamal encryption, Bayer-Groth verifiable shuffles, zero-knowledge proofs, return codes, and a full election ceremony demo.
1041 lines
46 KiB
HTML
1041 lines
46 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Swiss Post E-Voting: A Cryptographic Election</title>
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body {
|
|
background: #0d1117;
|
|
color: #c9d1d9;
|
|
font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', 'JetBrains Mono', 'Consolas', monospace;
|
|
font-size: 15px;
|
|
line-height: 1.6;
|
|
overflow: hidden;
|
|
height: 100vh;
|
|
}
|
|
#progress-bar {
|
|
position: fixed; top: 0; left: 0;
|
|
height: 3px; background: #58a6ff;
|
|
transition: width 0.4s ease;
|
|
z-index: 100;
|
|
}
|
|
#slide-counter {
|
|
position: fixed; top: 10px; right: 20px;
|
|
font-size: 12px; color: #484f58;
|
|
z-index: 100;
|
|
}
|
|
#nav-hint {
|
|
position: fixed; bottom: 15px; right: 20px;
|
|
font-size: 11px; color: #30363d;
|
|
z-index: 100;
|
|
}
|
|
#stage {
|
|
max-width: 920px;
|
|
margin: 0 auto;
|
|
padding: 40px 30px 60px;
|
|
height: 100vh;
|
|
overflow-y: auto;
|
|
scrollbar-width: thin;
|
|
scrollbar-color: #21262d #0d1117;
|
|
}
|
|
#stage::-webkit-scrollbar { width: 6px; }
|
|
#stage::-webkit-scrollbar-track { background: #0d1117; }
|
|
#stage::-webkit-scrollbar-thumb { background: #21262d; border-radius: 3px; }
|
|
|
|
.role-banner {
|
|
display: inline-block;
|
|
padding: 4px 14px;
|
|
border-radius: 4px;
|
|
font-size: 12px;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 1.5px;
|
|
margin-bottom: 16px;
|
|
}
|
|
.slide-title {
|
|
font-size: 22px;
|
|
font-weight: 700;
|
|
margin-bottom: 20px;
|
|
color: #e6edf3;
|
|
}
|
|
.content { margin-bottom: 20px; }
|
|
|
|
.decision-box {
|
|
border: 1px solid #30363d;
|
|
border-radius: 8px;
|
|
padding: 16px 20px;
|
|
margin: 20px 0;
|
|
background: #161b22;
|
|
}
|
|
.decision-question {
|
|
color: #8b949e;
|
|
font-size: 14px;
|
|
margin-bottom: 10px;
|
|
}
|
|
.decision-answer {
|
|
font-size: 16px;
|
|
font-weight: 700;
|
|
padding: 6px 16px;
|
|
border-radius: 4px;
|
|
display: inline-block;
|
|
}
|
|
|
|
.crypto-block {
|
|
background: #161b22;
|
|
border: 1px solid #21262d;
|
|
border-radius: 6px;
|
|
padding: 14px 18px;
|
|
margin: 12px 0;
|
|
font-size: 13px;
|
|
overflow-x: auto;
|
|
white-space: pre;
|
|
line-height: 1.5;
|
|
}
|
|
.crypto-block .label { color: #8b949e; }
|
|
.crypto-block .value { color: #79c0ff; }
|
|
.crypto-block .secret { color: #f85149; }
|
|
.crypto-block .public { color: #56d364; }
|
|
.crypto-block .highlight { color: #d2a8ff; }
|
|
.crypto-block .dim { color: #484f58; }
|
|
|
|
.card-box {
|
|
border: 2px solid #f0c040;
|
|
border-radius: 8px;
|
|
padding: 16px 20px;
|
|
margin: 14px 0;
|
|
background: #1c1a12;
|
|
white-space: pre;
|
|
font-size: 13px;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.proof-line { margin: 2px 0; font-size: 13px; }
|
|
.proof-pass { color: #56d364; font-weight: 700; }
|
|
.proof-tree { color: #484f58; }
|
|
|
|
.result-box {
|
|
border: 2px solid #58a6ff;
|
|
border-radius: 8px;
|
|
padding: 20px 24px;
|
|
margin: 16px 0;
|
|
background: #0d1926;
|
|
text-align: center;
|
|
}
|
|
.result-bar {
|
|
display: inline-block;
|
|
height: 18px;
|
|
border-radius: 3px;
|
|
margin-left: 8px;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
.verdict-box {
|
|
border: 2px solid #56d364;
|
|
border-radius: 8px;
|
|
padding: 24px 28px;
|
|
margin: 16px 0;
|
|
background: #0d2818;
|
|
font-size: 14px;
|
|
line-height: 1.7;
|
|
}
|
|
|
|
.voter-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin: 12px 0;
|
|
font-size: 13px;
|
|
}
|
|
.voter-table th, .voter-table td {
|
|
padding: 6px 12px;
|
|
text-align: left;
|
|
border-bottom: 1px solid #21262d;
|
|
}
|
|
.voter-table th { color: #8b949e; font-weight: 600; }
|
|
|
|
/* Title card */
|
|
.title-card {
|
|
text-align: center;
|
|
padding-top: 15vh;
|
|
}
|
|
.title-card h1 {
|
|
font-size: 32px;
|
|
color: #e6edf3;
|
|
margin-bottom: 12px;
|
|
font-weight: 800;
|
|
}
|
|
.title-card .subtitle {
|
|
font-size: 16px;
|
|
color: #8b949e;
|
|
margin-bottom: 40px;
|
|
}
|
|
.title-card .swiss-cross {
|
|
font-size: 60px;
|
|
color: #f85149;
|
|
margin-bottom: 30px;
|
|
}
|
|
.title-card .start-hint {
|
|
color: #484f58;
|
|
font-size: 13px;
|
|
margin-top: 50px;
|
|
animation: pulse 2s ease-in-out infinite;
|
|
}
|
|
@keyframes pulse {
|
|
0%, 100% { opacity: 0.4; }
|
|
50% { opacity: 1; }
|
|
}
|
|
|
|
/* Role colors */
|
|
.role-chancellor { background: #f0c040; color: #0d1117; }
|
|
.role-cc { background: #00ff88; color: #0d1117; }
|
|
.role-voter { background: #60a0ff; color: #0d1117; }
|
|
.role-eb { background: #ff8844; color: #0d1117; }
|
|
.role-auditor { background: #ff4466; color: #0d1117; }
|
|
.role-system { background: #555; color: #e6edf3; }
|
|
.role-none { background: #30363d; color: #e6edf3; }
|
|
|
|
.color-chancellor { color: #f0c040; }
|
|
.color-cc { color: #00ff88; }
|
|
.color-voter { color: #60a0ff; }
|
|
.color-eb { color: #ff8844; }
|
|
.color-auditor { color: #ff4466; }
|
|
.color-system { color: #aaaaaa; }
|
|
|
|
.fade-in { animation: fadeIn 0.3s ease-in; }
|
|
@keyframes fadeIn {
|
|
from { opacity: 0; transform: translateY(12px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
.em { color: #e6edf3; font-weight: 600; }
|
|
.dim { color: #484f58; }
|
|
.note { color: #8b949e; font-size: 13px; font-style: italic; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div id="progress-bar"></div>
|
|
<div id="slide-counter"></div>
|
|
<div id="nav-hint">Space / Enter / Arrow keys to navigate</div>
|
|
<div id="stage"></div>
|
|
|
|
<script>
|
|
const SLIDES = [
|
|
// 0: Title
|
|
{
|
|
role: null,
|
|
html: `
|
|
<div class="title-card">
|
|
<div class="swiss-cross">🇨🇭</div>
|
|
<h1>Swiss Post E-Voting</h1>
|
|
<div class="subtitle">A Cryptographic Election in 27 Steps</div>
|
|
<div style="color:#484f58; font-size:13px; margin-top:10px;">
|
|
6 voters · 2 candidates · 4 control components · 5 shuffle proofs
|
|
</div>
|
|
<div class="start-hint">Press Space to begin</div>
|
|
</div>
|
|
`
|
|
},
|
|
|
|
// 1: Canton Chancellor - Open e-voting
|
|
{
|
|
role: 'Canton Chancellor',
|
|
roleClass: 'role-chancellor',
|
|
colorClass: 'color-chancellor',
|
|
title: 'Opening the Electronic Voting Channel',
|
|
html: `
|
|
<p>You are the <span class="em color-chancellor">Canton Chancellor</span>, the highest authority for this election.</p>
|
|
<p style="margin-top:12px">A federal referendum is coming. Before any cryptographic key is generated, before any ballot exists, you must authorize the opening of the electronic voting channel.</p>
|
|
<div class="decision-box">
|
|
<div class="decision-question">Should you open the electronic voting channel for this election?</div>
|
|
<div class="decision-answer role-chancellor">Yes, open e-voting</div>
|
|
</div>
|
|
<p class="note">The Canton Chancellor's authorization begins the entire ceremony.</p>
|
|
`
|
|
},
|
|
|
|
// 2: Canton Chancellor - Number of candidates
|
|
{
|
|
role: 'Canton Chancellor',
|
|
roleClass: 'role-chancellor',
|
|
colorClass: 'color-chancellor',
|
|
title: 'Defining the Ballot',
|
|
html: `
|
|
<p>How many candidates should appear on the ballot?</p>
|
|
<div class="decision-box">
|
|
<div class="decision-question">How many candidates should be on the ballot?</div>
|
|
<div class="decision-answer role-chancellor">2 candidates: Alice & Bob</div>
|
|
</div>
|
|
`
|
|
},
|
|
|
|
// 3: Canton Chancellor - Sign crypto parameters
|
|
{
|
|
role: 'Canton Chancellor',
|
|
roleClass: 'role-chancellor',
|
|
colorClass: 'color-chancellor',
|
|
title: 'Signing the Cryptographic Group Parameters',
|
|
html: `
|
|
<p>The system needs a <span class="em">safe prime group</span> for ElGamal encryption. These parameters define the mathematical universe in which all cryptography will happen.</p>
|
|
<div class="decision-box">
|
|
<div class="decision-question">Sign the order to generate the cryptographic group parameters?</div>
|
|
<div class="decision-answer role-chancellor">Sign</div>
|
|
</div>
|
|
<div class="crypto-block"><span class="label">q</span> = <span class="value">102456006687220362937022221649168886313301978055082970105502958159943003448101</span>
|
|
<span class="label">p</span> = <span class="value">204912013374440725874044443298337772626603956110165940211005916319886006896203</span>
|
|
<span class="label">g</span> = <span class="value">4</span> <span class="dim">(generator)</span>
|
|
|
|
<span class="dim">q is 256 bits, p is 257 bits
|
|
p = 2q + 1 (safe prime, confirmed)
|
|
Both prime (confirmed)</span></div>
|
|
<p class="note">In production: |p| = 3072 bits. This demo uses 256-bit for readability.</p>
|
|
`
|
|
},
|
|
|
|
// 4: CC0 - Generate key
|
|
{
|
|
role: 'CC0 Operator (Bern)',
|
|
roleClass: 'role-cc',
|
|
colorClass: 'color-cc',
|
|
title: 'Generating Your Secret Key',
|
|
html: `
|
|
<p>You are now the <span class="em color-cc">CC0 Operator in Bern</span>. You sit in a secure room with an air-gapped machine. Your job: generate a secret key that <em>only you</em> will ever know.</p>
|
|
<div class="decision-box">
|
|
<div class="decision-question">Ready to generate your key pair?</div>
|
|
<div class="decision-answer role-cc">Generate my key</div>
|
|
</div>
|
|
<div class="crypto-block"><span class="label">=== YOUR SECRET KEY (CC0, Bern) ===</span>
|
|
|
|
<span class="label">Secret key [0]:</span> <span class="secret">490298081064930195553874605087930422904...</span>
|
|
<span class="dim">(77 digits, only you can see this)</span>
|
|
<span class="label">Secret key [1]:</span> <span class="secret">906260723715705230338812685464779149349...</span>
|
|
<span class="dim">(77 digits, only you can see this)</span>
|
|
|
|
<span class="label">=== YOUR PUBLIC KEY (CC0, Bern) ===</span>
|
|
|
|
<span class="label">Public key [0]:</span> <span class="public">164907173125453164942397900344209023580...</span>
|
|
<span class="dim">Computed as: g ^ secret_key[0] mod p</span>
|
|
<span class="label">Public key [1]:</span> <span class="public">865964935823822544135231120873786690830...</span>
|
|
<span class="dim">Computed as: g ^ secret_key[1] mod p</span></div>
|
|
<p class="note">The public key can be shared with everyone. The secret key never leaves this machine.</p>
|
|
`
|
|
},
|
|
|
|
// 5: CC0 - Schnorr proof
|
|
{
|
|
role: 'CC0 Operator (Bern)',
|
|
roleClass: 'role-cc',
|
|
colorClass: 'color-cc',
|
|
title: 'Generating a Schnorr Proof of Knowledge',
|
|
html: `
|
|
<p>You must <em>prove</em> you know your secret key without revealing it. This is a <span class="em">zero-knowledge proof</span>.</p>
|
|
<div class="decision-box">
|
|
<div class="decision-question">Generate the Schnorr proof?</div>
|
|
<div class="decision-answer role-cc">Generate proof</div>
|
|
</div>
|
|
<div class="crypto-block"><span class="label">--- Schnorr Proof Generation ---</span>
|
|
|
|
<span class="highlight">Step 1:</span> Pick a random number 'b' (kept secret)
|
|
b = <span class="secret">577282606851693328110434804545...</span> <span class="dim">(secret, thrown away after)</span>
|
|
|
|
<span class="highlight">Step 2:</span> Compute commitment c = g^b mod p
|
|
c = <span class="public">196993736651760197696345266502366676991...</span>
|
|
<span class="dim">(This is like sealing your answer in an envelope)</span>
|
|
|
|
<span class="highlight">Step 3:</span> Compute challenge e = Hash(p, q, g, public_key, c)
|
|
e = <span class="value">280555736662452288549346417809892436748...</span>
|
|
<span class="dim">(The hash makes this unpredictable -- you can't cheat)</span>
|
|
|
|
<span class="highlight">Step 4:</span> Compute response z = b + e * secret_key mod q
|
|
z = <span class="value">137947587127192694451619751067570738829...</span>
|
|
|
|
<span class="label">=== YOUR SCHNORR PROOF ===</span>
|
|
e = <span class="value">280555736662452288549346417809892436748...</span>
|
|
z = <span class="value">137947587127192694451619751067570738829...</span>
|
|
|
|
<span class="dim">Anyone can verify: g^z =?= c * public_key^e
|
|
If it checks out, you MUST know the secret.
|
|
But e and z reveal NOTHING about what the secret is.</span>
|
|
|
|
Self-check: g^z == c * PK^e <span class="public">PROOF VALID</span></div>
|
|
`
|
|
},
|
|
|
|
// 6: CC0 - Hand over to CC1-3
|
|
{
|
|
role: 'CC0 Operator (Bern)',
|
|
roleClass: 'role-cc',
|
|
colorClass: 'color-cc',
|
|
title: 'Handing Over to the Other Control Components',
|
|
html: `
|
|
<p>Three more operators in Zurich, Geneva, and Lugano must do the same: generate keys and proofs on their air-gapped machines.</p>
|
|
<div class="decision-box">
|
|
<div class="decision-question">Hand over control to CC1, CC2, CC3?</div>
|
|
<div class="decision-answer role-cc">Skip to all done</div>
|
|
</div>
|
|
<div class="crypto-block"><span class="label">CC1 (Zurich):</span> Key generated + Schnorr proof <span class="public">VALID</span>
|
|
<span class="label">CC2 (Geneva):</span> Key generated + Schnorr proof <span class="public">VALID</span>
|
|
<span class="label">CC3 (Lugano):</span> Key generated + Schnorr proof <span class="public">VALID</span></div>
|
|
<p class="note">4 independent operators in 4 Swiss cities. No single one can decrypt anything alone.</p>
|
|
`
|
|
},
|
|
|
|
// 7: EB President - Generate EB key
|
|
{
|
|
role: 'EB President',
|
|
roleClass: 'role-eb',
|
|
colorClass: 'color-eb',
|
|
title: 'Electoral Board Key Generation',
|
|
html: `
|
|
<p>You are the <span class="em color-eb">Electoral Board President</span>. You and your board members each enter a password. Combined, these passwords derive the 5th secret key.</p>
|
|
<div class="decision-box">
|
|
<div class="decision-question">Set passwords and generate the Electoral Board key?</div>
|
|
<div class="decision-answer role-eb">Set passwords & generate</div>
|
|
</div>
|
|
<div class="crypto-block"><span class="label">Electoral Board key generated.</span>
|
|
<span class="label">PK4 (Electoral Board):</span> <span class="public">111235888017674427027755775458...</span>
|
|
|
|
<span class="dim">This key is split across multiple board members' passwords.
|
|
On election night, ALL board members must enter their passwords
|
|
to reconstruct this key for the final decryption.</span></div>
|
|
`
|
|
},
|
|
|
|
// 8: System Admin - Combine keys
|
|
{
|
|
role: 'System Administrator',
|
|
roleClass: 'role-system',
|
|
colorClass: 'color-system',
|
|
title: 'Combining All 5 Public Keys',
|
|
html: `
|
|
<p>You are the <span class="em">System Administrator</span>. You combine all 5 public keys into one <span class="em">Election Public Key</span>.</p>
|
|
<div class="decision-box">
|
|
<div class="decision-question">Combine the 5 public keys?</div>
|
|
<div class="decision-answer role-system">Combine</div>
|
|
</div>
|
|
<div class="crypto-block"><span class="label"> PK0 CC0 (Bern) =</span> <span class="public">190643356536098883252739444301...</span>
|
|
<span class="label"> PK1 CC1 (Zurich) =</span> <span class="public">763639679015419285323116529301...</span>
|
|
<span class="label"> PK2 CC2 (Geneva) =</span> <span class="public">166178915117197264285723029147...</span>
|
|
<span class="label"> PK3 CC3 (Lugano) =</span> <span class="public">500402434679977682038657772038...</span>
|
|
<span class="label"> PK4 Electoral Board =</span> <span class="public">111235888017674427027755775458...</span>
|
|
|
|
<span class="dim">Multiplying all 5 together...</span>
|
|
|
|
<span class="label">Election Public Key =</span> <span class="highlight">187387247443810368528889414473024386338505450542...</span>
|
|
|
|
<span class="dim">This single key locks the ballot box.
|
|
To unlock it, you need ALL 5 secret keys working together.</span></div>
|
|
`
|
|
},
|
|
|
|
// 9: System Admin - Encode candidates
|
|
{
|
|
role: 'System Administrator',
|
|
roleClass: 'role-system',
|
|
colorClass: 'color-system',
|
|
title: 'Encoding the Candidates',
|
|
html: `
|
|
<p>Each candidate is mapped to a prime number, then squared to create a <span class="em">quadratic residue</span> in the group. This encoding enables efficient tallying after decryption.</p>
|
|
<div class="decision-box">
|
|
<div class="decision-question">Encode the candidates?</div>
|
|
<div class="decision-answer role-system">Encode candidates</div>
|
|
</div>
|
|
<div class="crypto-block"><span class="label">Candidate encoding:</span>
|
|
|
|
<span class="value">Alice</span> = prime 2 → squared: <span class="highlight">4</span> <span class="dim">(2² = 4)</span>
|
|
<span class="value">Bob</span> = prime 3 → squared: <span class="highlight">9</span> <span class="dim">(3² = 9)</span></div>
|
|
<p class="note">In the production system, the encoding uses larger primes for more complex ballots.</p>
|
|
`
|
|
},
|
|
|
|
// 10: Registry Officer - Show one card
|
|
{
|
|
role: 'Registry Officer',
|
|
roleClass: 'role-chancellor',
|
|
colorClass: 'color-chancellor',
|
|
title: 'Generating Voting Cards',
|
|
html: `
|
|
<p>You are the <span class="em color-chancellor">Registry Officer</span>. Each voter receives a physical card in the mail with their secret codes.</p>
|
|
<div class="decision-box">
|
|
<div class="decision-question">Generate the 6 voting cards?</div>
|
|
<div class="decision-answer role-chancellor">Show me one card first</div>
|
|
</div>
|
|
<div class="card-box"> +------------------------------------------------------+
|
|
| SWISS CONFEDERATION |
|
|
| Electronic Voting Card |
|
|
| |
|
|
| Voter ID: voter-0000 |
|
|
| Start Voting Key: <span class="highlight">SVK-0000</span> |
|
|
| Ballot Casting Key: <span class="highlight">BCK-0000</span> |
|
|
| |
|
|
| Choice Return Codes (to verify your vote): |
|
|
| Alice: <span class="value">CC00</span> |
|
|
| Bob: <span class="value">CC01</span> |
|
|
| |
|
|
| Vote Cast Code: <span class="value">VCC00</span> |
|
|
| |
|
|
| KEEP THIS CARD SAFE. DO NOT SHARE IT. |
|
|
+------------------------------------------------------+</div>
|
|
<p class="note">The codes on this card are the voter's only way to verify their vote was recorded correctly.</p>
|
|
`
|
|
},
|
|
|
|
// 11: Registry Officer - Generate all 6 cards
|
|
{
|
|
role: 'Registry Officer',
|
|
roleClass: 'role-chancellor',
|
|
colorClass: 'color-chancellor',
|
|
title: 'Mailing All 6 Voting Cards',
|
|
html: `
|
|
<div class="decision-box">
|
|
<div class="decision-question">Generate the remaining 5 cards and mail all 6?</div>
|
|
<div class="decision-answer role-chancellor">Generate & mail all 6</div>
|
|
</div>
|
|
<table class="voter-table">
|
|
<tr><th>Voter</th><th>SVK</th><th>BCK</th><th>Alice</th><th>Bob</th><th>VCC</th></tr>
|
|
<tr><td>Anna</td><td>SVK-0000</td><td>BCK-0000</td><td>CC00</td><td>CC01</td><td>VCC00</td></tr>
|
|
<tr><td>Beat</td><td>SVK-0001</td><td>BCK-0001</td><td>CC10</td><td>CC11</td><td>VCC01</td></tr>
|
|
<tr><td>Clara</td><td>SVK-0002</td><td>BCK-0002</td><td>CC20</td><td>CC21</td><td>VCC02</td></tr>
|
|
<tr><td>Daniel</td><td>SVK-0003</td><td>BCK-0003</td><td>CC30</td><td>CC31</td><td>VCC03</td></tr>
|
|
<tr><td>Eva</td><td>SVK-0004</td><td>BCK-0004</td><td>CC40</td><td>CC41</td><td>VCC04</td></tr>
|
|
<tr><td>Fritz</td><td>SVK-0005</td><td>BCK-0005</td><td>CC50</td><td>CC51</td><td>VCC05</td></tr>
|
|
</table>
|
|
<p class="note">Each voter's codes are unique. Only they know their return codes.</p>
|
|
`
|
|
},
|
|
|
|
// 12: Voter Anna - Authenticate
|
|
{
|
|
role: 'Voter Anna',
|
|
roleClass: 'role-voter',
|
|
colorClass: 'color-voter',
|
|
title: 'Authentication',
|
|
html: `
|
|
<p>You are <span class="em color-voter">Anna</span>, a Swiss citizen. You received your voting card in the mail. You open your browser and navigate to the voting portal.</p>
|
|
<div class="decision-box">
|
|
<div class="decision-question">Enter your credentials to authenticate?</div>
|
|
<div class="decision-answer role-voter">Enter SVK-0000 + DOB</div>
|
|
</div>
|
|
<div class="crypto-block"><span class="public">Authentication successful.</span>
|
|
<span class="dim">Your Start Voting Key was verified using Argon2id (password hash).
|
|
The server never stores your SVK in plaintext.</span>
|
|
|
|
<span class="label">Ballot loaded:</span>
|
|
[ ] Alice
|
|
[ ] Bob</div>
|
|
`
|
|
},
|
|
|
|
// 13: Voter Anna - Vote + encryption
|
|
{
|
|
role: 'Voter Anna',
|
|
roleClass: 'role-voter',
|
|
colorClass: 'color-voter',
|
|
title: 'Casting Your Vote',
|
|
html: `
|
|
<div class="decision-box">
|
|
<div class="decision-question">Who do you vote for?</div>
|
|
<div class="decision-answer role-voter">Alice</div>
|
|
</div>
|
|
<div class="crypto-block"><span class="label">=== INSIDE YOUR BROWSER ===</span>
|
|
|
|
<span class="highlight">Step 1:</span> Encode your choice
|
|
Alice → prime 2 → squared: <span class="value">4</span>
|
|
Your vote is the number: <span class="value">4</span>
|
|
|
|
<span class="highlight">Step 2:</span> Generate random number 'r' (used once, then discarded)
|
|
r = <span class="secret">833704513946265251405968611993787771762...</span>
|
|
<span class="dim">This randomness makes YOUR ciphertext unique.
|
|
Even if someone else also votes Alice, their
|
|
encrypted ballot will look completely different.</span>
|
|
|
|
<span class="highlight">Step 3:</span> ElGamal encryption
|
|
gamma = g^r mod p
|
|
= <span class="value">183571083508143527798599308812874641392...</span>
|
|
|
|
phi = ElectionPK^r × vote mod p
|
|
= <span class="value">136498980446327435592002680136840893911...</span>
|
|
|
|
<span class="dim">Your vote (4) is now buried inside 'phi',
|
|
hidden behind ElectionPK^r -- a huge random-looking number.
|
|
To extract the 4, you'd need to compute gamma^(sum of all 5 secrets),
|
|
which requires ALL 5 key holders to cooperate.</span></div>
|
|
<div class="crypto-block"><span class="label">=== WHAT GETS SENT TO THE SERVER ===</span>
|
|
|
|
gamma = <span class="value">183571083508143527798599308812874641392...</span>
|
|
phi = <span class="value">136498980446327435592002680136840893911...</span>
|
|
|
|
<span class="dim">That's it. Two big numbers. Pure noise to anyone
|
|
who doesn't have all 5 secret keys.</span></div>
|
|
`
|
|
},
|
|
|
|
// 14: Voter Anna - Return code + confirm
|
|
{
|
|
role: 'Voter Anna',
|
|
roleClass: 'role-voter',
|
|
colorClass: 'color-voter',
|
|
title: 'Verifying with Return Codes',
|
|
html: `
|
|
<p>The server processes your encrypted vote through the 4 control components. Each one computes a partial return code. Combined, they produce a <span class="em">Choice Return Code</span> that appears on your screen.</p>
|
|
<div class="crypto-block"><span class="label">Server displays:</span> Choice Return Code = <span class="highlight">CC00</span>
|
|
|
|
<span class="dim">You look at your physical voting card...</span>
|
|
<span class="label">Your card says:</span> Alice = <span class="highlight">CC00</span> <span class="public">MATCH!</span>
|
|
|
|
<span class="dim">This confirms the server recorded "Alice" -- not something else.
|
|
No single component could fake this code. All 4 CCs contributed.</span></div>
|
|
<div class="decision-box">
|
|
<div class="decision-question">Return code matches. Confirm your vote?</div>
|
|
<div class="decision-answer role-voter">Enter BCK-0000 to confirm</div>
|
|
</div>
|
|
<div class="crypto-block"><span class="label">Server displays:</span> Vote Cast Code = <span class="highlight">VCC00</span>
|
|
<span class="label">Your card says:</span> VCC = <span class="highlight">VCC00</span> <span class="public">MATCH!</span>
|
|
|
|
<span class="dim">Your vote is sealed. Done.</span></div>
|
|
`
|
|
},
|
|
|
|
// 15: Voter Beat
|
|
{
|
|
role: 'Voter Beat',
|
|
roleClass: 'role-voter',
|
|
colorClass: 'color-voter',
|
|
title: 'Beat Votes',
|
|
html: `
|
|
<div class="decision-box">
|
|
<div class="decision-question">Who do you vote for?</div>
|
|
<div class="decision-answer role-voter">Alice</div>
|
|
</div>
|
|
<div class="crypto-block"><span class="dim">Authenticate with SVK-0001...</span> <span class="public">OK</span>
|
|
<span class="dim">Encrypt vote (Alice = 4)...</span> <span class="public">OK</span>
|
|
<span class="dim">Return code CC10...</span> <span class="public">MATCH</span>
|
|
<span class="dim">Confirm with BCK-0001...</span> <span class="public">OK</span>
|
|
<span class="dim">Vote Cast Code VCC01...</span> <span class="public">MATCH</span></div>
|
|
`
|
|
},
|
|
|
|
// 16: Voter Clara
|
|
{
|
|
role: 'Voter Clara',
|
|
roleClass: 'role-voter',
|
|
colorClass: 'color-voter',
|
|
title: 'Clara Votes',
|
|
html: `
|
|
<div class="decision-box">
|
|
<div class="decision-question">Who do you vote for?</div>
|
|
<div class="decision-answer role-voter">Alice</div>
|
|
</div>
|
|
<div class="crypto-block"><span class="dim">Authenticate with SVK-0002...</span> <span class="public">OK</span>
|
|
<span class="dim">Encrypt vote (Alice = 4)...</span> <span class="public">OK</span>
|
|
<span class="dim">Return code CC20...</span> <span class="public">MATCH</span>
|
|
<span class="dim">Confirm with BCK-0002...</span> <span class="public">OK</span>
|
|
<span class="dim">Vote Cast Code VCC02...</span> <span class="public">MATCH</span></div>
|
|
`
|
|
},
|
|
|
|
// 17: Voter Daniel
|
|
{
|
|
role: 'Voter Daniel',
|
|
roleClass: 'role-voter',
|
|
colorClass: 'color-voter',
|
|
title: 'Daniel Votes',
|
|
html: `
|
|
<div class="decision-box">
|
|
<div class="decision-question">Who do you vote for?</div>
|
|
<div class="decision-answer role-voter">Alice</div>
|
|
</div>
|
|
<div class="crypto-block"><span class="dim">Authenticate with SVK-0003...</span> <span class="public">OK</span>
|
|
<span class="dim">Encrypt vote (Alice = 4)...</span> <span class="public">OK</span>
|
|
<span class="dim">Return code CC30...</span> <span class="public">MATCH</span>
|
|
<span class="dim">Confirm with BCK-0003...</span> <span class="public">OK</span>
|
|
<span class="dim">Vote Cast Code VCC03...</span> <span class="public">MATCH</span></div>
|
|
`
|
|
},
|
|
|
|
// 18: Voter Eva
|
|
{
|
|
role: 'Voter Eva',
|
|
roleClass: 'role-voter',
|
|
colorClass: 'color-voter',
|
|
title: 'Eva Votes',
|
|
html: `
|
|
<div class="decision-box">
|
|
<div class="decision-question">Who do you vote for?</div>
|
|
<div class="decision-answer role-voter">Alice</div>
|
|
</div>
|
|
<div class="crypto-block"><span class="dim">Authenticate with SVK-0004...</span> <span class="public">OK</span>
|
|
<span class="dim">Encrypt vote (Alice = 4)...</span> <span class="public">OK</span>
|
|
<span class="dim">Return code CC40...</span> <span class="public">MATCH</span>
|
|
<span class="dim">Confirm with BCK-0004...</span> <span class="public">OK</span>
|
|
<span class="dim">Vote Cast Code VCC04...</span> <span class="public">MATCH</span></div>
|
|
`
|
|
},
|
|
|
|
// 19: Voter Fritz
|
|
{
|
|
role: 'Voter Fritz',
|
|
roleClass: 'role-voter',
|
|
colorClass: 'color-voter',
|
|
title: 'Fritz Votes',
|
|
html: `
|
|
<p class="note" style="margin-bottom:12px">The last voter. Will he break the streak?</p>
|
|
<div class="decision-box">
|
|
<div class="decision-question">Who do you vote for?</div>
|
|
<div class="decision-answer role-voter">Bob</div>
|
|
</div>
|
|
<div class="crypto-block"><span class="dim">Authenticate with SVK-0005...</span> <span class="public">OK</span>
|
|
<span class="dim">Encrypt vote (Bob = 9)...</span> <span class="public">OK</span>
|
|
<span class="dim">Return code CC51...</span> <span class="public">MATCH</span>
|
|
<span class="dim">Confirm with BCK-0005...</span> <span class="public">OK</span>
|
|
<span class="dim">Vote Cast Code VCC05...</span> <span class="public">MATCH</span></div>
|
|
`
|
|
},
|
|
|
|
// 20: Voting closed
|
|
{
|
|
role: null,
|
|
roleClass: 'role-none',
|
|
title: 'Voting Period Closed',
|
|
html: `
|
|
<p style="font-size:18px; color:#e6edf3; margin-bottom:20px;">6 ballots cast. The voting period is over.</p>
|
|
<div class="crypto-block"><span class="label">What the server sees (hex dump of encrypted ballots):</span>
|
|
|
|
<span class="dim">Voter 0 | Alice |</span> <span class="value">E01E83B2B8...B8A0745F41</span>
|
|
<span class="dim">Voter 1 | Alice |</span> <span class="value">DE73EEB649...C3E0A5FAF2</span>
|
|
<span class="dim">Voter 2 | Alice |</span> <span class="value">FF1D9B7179...735424E93A</span>
|
|
<span class="dim">Voter 3 | Alice |</span> <span class="value">4931157C26...9D6A03E982</span>
|
|
<span class="dim">Voter 4 | Alice |</span> <span class="value">13C275835F...BF41E92D91</span>
|
|
<span class="dim">Voter 5 | Bob |</span> <span class="value">1232996CD9...3791B60DCF</span>
|
|
|
|
<span class="dim">Every ballot looks like random noise.
|
|
The server knows WHO voted, but not WHAT they voted.
|
|
Even the labels above (Alice/Bob) are invisible to the server.</span></div>
|
|
`
|
|
},
|
|
|
|
// 21: CC0 Shuffle
|
|
{
|
|
role: 'CC0 Operator (Bern)',
|
|
roleClass: 'role-cc',
|
|
colorClass: 'color-cc',
|
|
title: 'Shuffling the Ballots',
|
|
html: `
|
|
<p>Now you must <span class="em">shuffle</span> and <span class="em">re-encrypt</span> all 6 ballots. This breaks the link between voters and their encrypted votes.</p>
|
|
<div class="decision-box">
|
|
<div class="decision-question">Begin your shuffle?</div>
|
|
<div class="decision-answer role-cc">Shuffle now</div>
|
|
</div>
|
|
<div class="crypto-block"><span class="label">=== INPUT: 6 encrypted ballots (in voter order) ===</span>
|
|
|
|
<span class="dim">[0]</span> <span class="value">E01E83B2B8...B8A0745F41</span> <span class="dim">← Anna's ballot</span>
|
|
<span class="dim">[1]</span> <span class="value">DE73EEB649...C3E0A5FAF2</span> <span class="dim">← Beat's ballot</span>
|
|
<span class="dim">[2]</span> <span class="value">FF1D9B7179...735424E93A</span> <span class="dim">← Clara's ballot</span>
|
|
<span class="dim">[3]</span> <span class="value">4931157C26...9D6A03E982</span> <span class="dim">← Daniel's ballot</span>
|
|
<span class="dim">[4]</span> <span class="value">13C275835F...BF41E92D91</span> <span class="dim">← Eva's ballot</span>
|
|
<span class="dim">[5]</span> <span class="value">1232996CD9...3791B60DCF</span> <span class="dim">← Fritz's ballot</span>
|
|
|
|
<span class="dim">Generating random permutation...</span>
|
|
Secret permutation: <span class="secret">[0 2 4 3 5 1]</span>
|
|
<span class="dim">(This is destroyed after the proof is generated.)</span>
|
|
|
|
<span class="dim">Re-encrypting each ballot with fresh randomness...</span></div>
|
|
<div class="crypto-block"><span class="label">=== OUTPUT: 6 shuffled + re-encrypted ballots ===</span>
|
|
|
|
<span class="dim">[0]</span> <span class="value">D7B8715E1D...07B4756A00</span> <span class="dim">← which voter? Nobody knows.</span>
|
|
<span class="dim">[1]</span> <span class="value">19EC21D022...03FA6B7A94</span> <span class="dim">← which voter? Nobody knows.</span>
|
|
<span class="dim">[2]</span> <span class="value">1FC53796E9...50AD1138C8</span> <span class="dim">← which voter? Nobody knows.</span>
|
|
<span class="dim">[3]</span> <span class="value">88B2E59D8B...AB2F8F9CB6</span> <span class="dim">← which voter? Nobody knows.</span>
|
|
<span class="dim">[4]</span> <span class="value">F27A76A4D6...7E83ED14F6</span> <span class="dim">← which voter? Nobody knows.</span>
|
|
<span class="dim">[5]</span> <span class="value">14F747B54D...A2C76D0315</span> <span class="dim">← which voter? Nobody knows.</span>
|
|
|
|
<span class="dim">Compare input [0] to ANY output -- completely different numbers.
|
|
No way to match them.</span></div>
|
|
<div class="crypto-block"><span class="label">Generating Bayer-Groth shuffle proof...</span>
|
|
|
|
<span class="proof-tree">ShuffleArgument</span>
|
|
<span class="proof-tree">+-- ProductArgument</span>
|
|
<span class="proof-tree">| +-- HadamardArgument</span>
|
|
<span class="proof-tree">| | +-- ZeroArgument</span>
|
|
<span class="proof-tree">| +-- SingleValueProductArgument</span>
|
|
<span class="proof-tree">+-- MultiExponentiationArgument</span>
|
|
|
|
<span class="dim">This proof guarantees: same 6 votes, just reordered.
|
|
No votes added, removed, or changed.</span>
|
|
<span class="public">PROOF GENERATED</span> <span class="dim">(5ms)</span>
|
|
|
|
<span class="dim">Partial decryption: removing CC0's encryption layer...
|
|
4 layers remain (CC1 + CC2 + CC3 + Electoral Board)</span></div>
|
|
`
|
|
},
|
|
|
|
// 22: CC1-CC3 shuffle
|
|
{
|
|
role: null,
|
|
roleClass: 'role-none',
|
|
title: 'CC1, CC2, CC3 Shuffle in Sequence',
|
|
html: `
|
|
<p>Each control component receives the output of the previous one, shuffles again, re-encrypts, generates a proof, and removes its encryption layer.</p>
|
|
<div class="decision-box">
|
|
<div class="decision-question">Process the remaining 3 CC shuffles?</div>
|
|
<div class="decision-answer role-cc">Process all 3</div>
|
|
</div>
|
|
<div class="crypto-block"><span class="color-cc">CC1 (Zurich):</span> Shuffle <span class="public">OK</span> Re-encrypt <span class="public">OK</span> Partial decrypt <span class="public">OK</span> Proof <span class="public">OK</span>
|
|
<span class="dim">3 layers remain</span>
|
|
|
|
<span class="color-cc">CC2 (Geneva):</span> Shuffle <span class="public">OK</span> Re-encrypt <span class="public">OK</span> Partial decrypt <span class="public">OK</span> Proof <span class="public">OK</span>
|
|
<span class="dim">2 layers remain</span>
|
|
|
|
<span class="color-cc">CC3 (Lugano):</span> Shuffle <span class="public">OK</span> Re-encrypt <span class="public">OK</span> Partial decrypt <span class="public">OK</span> Proof <span class="public">OK</span>
|
|
<span class="dim">1 layer remains</span></div>
|
|
<p class="note">After 4 shuffles, the ballots have been permuted 4 times with 4 independent random permutations. The original order is mathematically unrecoverable.</p>
|
|
`
|
|
},
|
|
|
|
// 23: EB - Final shuffle + decrypt
|
|
{
|
|
role: 'EB President',
|
|
roleClass: 'role-eb',
|
|
colorClass: 'color-eb',
|
|
title: 'Opening the Ballot Box',
|
|
html: `
|
|
<p>The Electoral Board performs the 5th and final shuffle, then removes the last encryption layer. The ballots are now in plaintext -- but in a random order that nobody can trace back to voters.</p>
|
|
<div class="decision-box">
|
|
<div class="decision-question">Final shuffle + decrypt?</div>
|
|
<div class="decision-answer role-eb">Open the ballot box</div>
|
|
</div>
|
|
<div class="crypto-block"><span class="label">=== THE BALLOT BOX IS OPEN ===</span>
|
|
|
|
<span class="dim">Slot 0:</span> decrypted value = <span class="highlight">4</span> → <span class="value">Alice</span>
|
|
<span class="dim">Slot 1:</span> decrypted value = <span class="highlight">4</span> → <span class="value">Alice</span>
|
|
<span class="dim">Slot 2:</span> decrypted value = <span class="highlight">4</span> → <span class="value">Alice</span>
|
|
<span class="dim">Slot 3:</span> decrypted value = <span class="highlight">4</span> → <span class="value">Alice</span>
|
|
<span class="dim">Slot 4:</span> decrypted value = <span class="highlight">9</span> → <span class="value">Bob</span>
|
|
<span class="dim">Slot 5:</span> decrypted value = <span class="highlight">4</span> → <span class="value">Alice</span>
|
|
|
|
<span class="dim">The votes are in random order. Nobody can tell
|
|
which slot belongs to which voter.</span></div>
|
|
`
|
|
},
|
|
|
|
// 24: Election result
|
|
{
|
|
role: null,
|
|
roleClass: 'role-none',
|
|
title: 'Election Result',
|
|
html: `
|
|
<div class="result-box">
|
|
<div style="font-size:20px; font-weight:700; color:#e6edf3; margin-bottom:20px;">ELECTION RESULT</div>
|
|
<div style="text-align:left; display:inline-block;">
|
|
<div style="margin:8px 0; font-size:16px;">
|
|
<span class="value" style="display:inline-block; width:60px;">Alice</span>
|
|
<span class="em" style="display:inline-block; width:60px;">5 votes</span>
|
|
<span class="result-bar" style="width:250px; background:#56d364;"></span>
|
|
</div>
|
|
<div style="margin:8px 0; font-size:16px;">
|
|
<span class="value" style="display:inline-block; width:60px;">Bob</span>
|
|
<span class="em" style="display:inline-block; width:60px;">1 vote</span>
|
|
<span class="result-bar" style="width:50px; background:#60a0ff;"></span>
|
|
</div>
|
|
</div>
|
|
<div style="margin-top:20px; color:#56d364; font-weight:700; font-size:16px;">Alice is elected.</div>
|
|
</div>
|
|
<div class="crypto-block"><span class="label">Sanity check:</span>
|
|
Expected: Alice=5 Bob=1 <span class="dim">(from your votes)</span>
|
|
Got: Alice=5 Bob=1
|
|
<span class="public">RESULT MATCHES</span></div>
|
|
`
|
|
},
|
|
|
|
// 25: Public Auditor - Verification
|
|
{
|
|
role: 'Public Auditor',
|
|
roleClass: 'role-auditor',
|
|
colorClass: 'color-auditor',
|
|
title: 'Independent Verification',
|
|
html: `
|
|
<p>You are a <span class="em color-auditor">Public Auditor</span>. You have NO secret keys. You have NO special access. You only have the public data: the proofs, the public keys, and the encrypted/shuffled ballots.</p>
|
|
<p style="margin-top:10px">Can you verify the election is honest? <em>Yes.</em> That's the entire point.</p>
|
|
<div class="crypto-block"><span class="label">=== VERIFYING 4 KEY PROOFS (Schnorr) ===</span>
|
|
|
|
CC0 (Bern): g^z == c * PK^e <span class="proof-pass">[PASS]</span>
|
|
CC1 (Zurich): g^z == c * PK^e <span class="proof-pass">[PASS]</span>
|
|
CC2 (Geneva): g^z == c * PK^e <span class="proof-pass">[PASS]</span>
|
|
CC3 (Lugano): g^z == c * PK^e <span class="proof-pass">[PASS]</span></div>
|
|
<div class="crypto-block"><span class="label">=== VERIFYING 5 SHUFFLE PROOFS (Bayer-Groth) ===</span>
|
|
|
|
Shuffle 0 <span class="dim">(CC0, Bern)</span> -- 6 ciphertexts, 2x3 matrix
|
|
<span class="proof-tree">ProductArgument ..............</span> <span class="proof-pass">PASS</span>
|
|
<span class="proof-tree">HadamardArgument ...........</span> <span class="proof-pass">PASS</span>
|
|
<span class="proof-tree">ZeroArgument .............</span> <span class="proof-pass">PASS</span>
|
|
<span class="proof-tree">SingleValueProductArgument .</span> <span class="proof-pass">PASS</span>
|
|
<span class="proof-tree">MultiExponentiationArgument .</span> <span class="proof-pass">PASS</span>
|
|
==> <span class="proof-pass">VERIFIED</span> <span class="dim">(2ms)</span>
|
|
|
|
Shuffle 1 <span class="dim">(CC1, Zurich)</span> -- 6 ciphertexts, 2x3 matrix
|
|
<span class="proof-tree">ProductArgument ..............</span> <span class="proof-pass">PASS</span>
|
|
<span class="proof-tree">HadamardArgument ...........</span> <span class="proof-pass">PASS</span>
|
|
<span class="proof-tree">ZeroArgument .............</span> <span class="proof-pass">PASS</span>
|
|
<span class="proof-tree">SingleValueProductArgument .</span> <span class="proof-pass">PASS</span>
|
|
<span class="proof-tree">MultiExponentiationArgument .</span> <span class="proof-pass">PASS</span>
|
|
==> <span class="proof-pass">VERIFIED</span> <span class="dim">(2ms)</span>
|
|
|
|
Shuffle 2 <span class="dim">(CC2, Geneva)</span> -- 6 ciphertexts, 2x3 matrix
|
|
<span class="proof-tree">ProductArgument ..............</span> <span class="proof-pass">PASS</span>
|
|
<span class="proof-tree">HadamardArgument ...........</span> <span class="proof-pass">PASS</span>
|
|
<span class="proof-tree">ZeroArgument .............</span> <span class="proof-pass">PASS</span>
|
|
<span class="proof-tree">SingleValueProductArgument .</span> <span class="proof-pass">PASS</span>
|
|
<span class="proof-tree">MultiExponentiationArgument .</span> <span class="proof-pass">PASS</span>
|
|
==> <span class="proof-pass">VERIFIED</span> <span class="dim">(2ms)</span>
|
|
|
|
Shuffle 3 <span class="dim">(CC3, Lugano)</span> -- 6 ciphertexts, 2x3 matrix
|
|
<span class="proof-tree">ProductArgument ..............</span> <span class="proof-pass">PASS</span>
|
|
<span class="proof-tree">HadamardArgument ...........</span> <span class="proof-pass">PASS</span>
|
|
<span class="proof-tree">ZeroArgument .............</span> <span class="proof-pass">PASS</span>
|
|
<span class="proof-tree">SingleValueProductArgument .</span> <span class="proof-pass">PASS</span>
|
|
<span class="proof-tree">MultiExponentiationArgument .</span> <span class="proof-pass">PASS</span>
|
|
==> <span class="proof-pass">VERIFIED</span> <span class="dim">(2ms)</span>
|
|
|
|
Shuffle 4 <span class="dim">(Electoral Board)</span> -- 6 ciphertexts, 2x3 matrix
|
|
<span class="proof-tree">ProductArgument ..............</span> <span class="proof-pass">PASS</span>
|
|
<span class="proof-tree">HadamardArgument ...........</span> <span class="proof-pass">PASS</span>
|
|
<span class="proof-tree">ZeroArgument .............</span> <span class="proof-pass">PASS</span>
|
|
<span class="proof-tree">SingleValueProductArgument .</span> <span class="proof-pass">PASS</span>
|
|
<span class="proof-tree">MultiExponentiationArgument .</span> <span class="proof-pass">PASS</span>
|
|
==> <span class="proof-pass">VERIFIED</span> <span class="dim">(2ms)</span></div>
|
|
<div class="crypto-block"><span class="label">Count Verification:</span>
|
|
Decrypted ballots: 6
|
|
Ballots submitted: 6
|
|
==> <span class="proof-pass">PASS: Every ballot is accounted for.</span></div>
|
|
`
|
|
},
|
|
|
|
// 26: Final verdict
|
|
{
|
|
role: null,
|
|
roleClass: 'role-none',
|
|
title: 'The Verdict',
|
|
html: `
|
|
<div class="verdict-box">
|
|
<div style="text-align:center; font-size:18px; font-weight:700; color:#56d364; margin-bottom:16px;">ALL VERIFICATIONS PASSED.</div>
|
|
<p>As a public auditor, you have independently verified:</p>
|
|
<div style="margin:14px 0 14px 10px;">
|
|
<div style="margin:6px 0;"><span class="proof-pass">[x]</span> All 4 key holders proved they know their secrets</div>
|
|
<div style="margin:6px 0;"><span class="proof-pass">[x]</span> All 5 shuffles are mathematically honest</div>
|
|
<div style="margin:6px 0;"><span class="proof-pass">[x]</span> No votes were added, removed, or changed</div>
|
|
<div style="margin:6px 0;"><span class="proof-pass">[x]</span> The final count matches the number of ballots</div>
|
|
</div>
|
|
<p style="margin-top:14px;">You did this <span class="em">WITHOUT</span> any secret keys.</p>
|
|
<p>You did this <span class="em">WITHOUT</span> trusting anyone.</p>
|
|
<p style="margin-top:10px; color:#56d364; font-weight:600;">Pure mathematics.</p>
|
|
</div>
|
|
|
|
<div style="margin-top:24px; padding:20px; border:1px solid #21262d; border-radius:8px; background:#161b22;">
|
|
<div style="color:#8b949e; font-size:13px; margin-bottom:12px;">RECAP -- What you experienced:</div>
|
|
<div style="margin:10px 0;">
|
|
<span class="color-cc" style="font-weight:700;">AS THE CC OPERATOR:</span>
|
|
<span style="color:#c9d1d9;"> You generated keys, shuffled votes, created proofs. You never saw anyone's vote. Even you can't undo your own shuffle -- the permutation was destroyed.</span>
|
|
</div>
|
|
<div style="margin:10px 0;">
|
|
<span class="color-voter" style="font-weight:700;">AS THE VOTER:</span>
|
|
<span style="color:#c9d1d9;"> Your vote was encrypted in your browser. The server stored only noise. Return codes on your physical card confirmed it was recorded correctly.</span>
|
|
</div>
|
|
<div style="margin:10px 0;">
|
|
<span class="color-auditor" style="font-weight:700;">AS THE AUDITOR:</span>
|
|
<span style="color:#c9d1d9;"> You verified every step with public data and math. No trust required. No access to secrets. The proofs are either valid or they aren't. No gray area.</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="margin-top:24px; padding:16px; border:1px solid #30363d; border-radius:8px; text-align:center;">
|
|
<p style="color:#8b949e; font-size:13px;">This is the same protocol used in real Swiss federal elections.</p>
|
|
<p style="color:#8b949e; font-size:13px; margin-top:6px;">The production system: 14 repositories, 500,000+ lines, Windows + Kubernetes + 50GB RAM.</p>
|
|
<p style="color:#8b949e; font-size:13px; margin-top:6px;">This demo: one 15MB Go binary on a Mac.</p>
|
|
<p style="color:#e6edf3; font-size:14px; font-weight:600; margin-top:10px;">The math is identical.</p>
|
|
</div>
|
|
`
|
|
}
|
|
];
|
|
|
|
let currentSlide = 0;
|
|
const stage = document.getElementById('stage');
|
|
const progressBar = document.getElementById('progress-bar');
|
|
const slideCounter = document.getElementById('slide-counter');
|
|
|
|
function render() {
|
|
const slide = SLIDES[currentSlide];
|
|
const total = SLIDES.length;
|
|
|
|
progressBar.style.width = ((currentSlide / (total - 1)) * 100) + '%';
|
|
slideCounter.textContent = (currentSlide + 1) + ' / ' + total;
|
|
|
|
let html = '<div class="fade-in">';
|
|
|
|
if (slide.role) {
|
|
html += '<div class="role-banner ' + (slide.roleClass || 'role-none') + '">' + slide.role + '</div>';
|
|
}
|
|
|
|
if (slide.title) {
|
|
html += '<div class="slide-title">' + slide.title + '</div>';
|
|
}
|
|
|
|
html += '<div class="content">' + slide.html + '</div>';
|
|
html += '</div>';
|
|
|
|
stage.innerHTML = html;
|
|
stage.scrollTop = 0;
|
|
}
|
|
|
|
window.addEventListener('keydown', function(e) {
|
|
if (e.key === ' ' || e.key === 'Enter' || e.key === 'ArrowRight' || e.key === 'ArrowDown') {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
if (currentSlide < SLIDES.length - 1) {
|
|
currentSlide++;
|
|
render();
|
|
}
|
|
} else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
|
|
e.preventDefault();
|
|
if (currentSlide > 0) {
|
|
currentSlide--;
|
|
render();
|
|
}
|
|
} else if (e.key === 'Home') {
|
|
e.preventDefault();
|
|
currentSlide = 0;
|
|
render();
|
|
} else if (e.key === 'End') {
|
|
e.preventDefault();
|
|
currentSlide = SLIDES.length - 1;
|
|
render();
|
|
}
|
|
});
|
|
|
|
// Touch support for mobile
|
|
let touchStartX = 0;
|
|
document.addEventListener('touchstart', function(e) {
|
|
touchStartX = e.changedTouches[0].screenX;
|
|
});
|
|
document.addEventListener('touchend', function(e) {
|
|
const diff = e.changedTouches[0].screenX - touchStartX;
|
|
if (Math.abs(diff) > 50) {
|
|
if (diff < 0 && currentSlide < SLIDES.length - 1) {
|
|
currentSlide++;
|
|
render();
|
|
} else if (diff > 0 && currentSlide > 0) {
|
|
currentSlide--;
|
|
render();
|
|
}
|
|
} else {
|
|
// Tap to advance
|
|
if (currentSlide < SLIDES.length - 1) {
|
|
currentSlide++;
|
|
render();
|
|
}
|
|
}
|
|
});
|
|
|
|
render();
|
|
</script>
|
|
</body>
|
|
</html>
|