Skip to content
Open
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4b893c9
Add implementation plans for Helios Booth Lit Redesign
benadida Feb 8, 2026
4812fe5
feat(booth2026): initialize Vite + TypeScript + Lit project structure
benadida Feb 8, 2026
051a43d
feat(booth2026): copy jscrypto library and add TypeScript declarations
benadida Feb 8, 2026
e319da6
feat(booth2026): add base CSS styles
benadida Feb 8, 2026
77ac96f
feat(booth2026): add main entry point and booth-app shell component
benadida Feb 8, 2026
bff1b23
feat(booth2026): add crypto library script loading to index.html
benadida Feb 8, 2026
30d5726
feat(booth2026): add Django URL route for new booth
benadida Feb 8, 2026
39ef491
chore(booth2026): add .gitignore and track package-lock.json
benadida Feb 8, 2026
cec180a
fix(booth2026): address code review feedback from Phase 1
benadida Feb 8, 2026
c977efc
feat(booth2026): add question-screen component with answer selection
benadida Feb 8, 2026
391ac5f
feat(booth2026): integrate question-screen with answer tracking and n…
benadida Feb 8, 2026
f6f613c
feat(booth2026): add beforeunload warning for in-progress ballots
benadida Feb 8, 2026
88bce39
fix(booth2026): address Phase 2 code review feedback
benadida Feb 8, 2026
8133b38
feat(booth2026): add encryption Web Worker
benadida Feb 8, 2026
febaabd
feat(booth2026): add worker message types to crypto declarations
benadida Feb 8, 2026
9f950a5
feat(booth2026): add review-screen component with ballot summary
benadida Feb 8, 2026
6ba0301
feat(booth2026): add submit-screen component
benadida Feb 8, 2026
04f5649
feat(booth2026): add audit-screen component for ballot spoiling
benadida Feb 8, 2026
6794632
feat(booth2026): add encrypting-screen with progress display
benadida Feb 8, 2026
49556a7
feat(booth2026): integrate crypto screens and encryption workflow
benadida Feb 8, 2026
e422bee
fix(booth2026): address Phase 3 code review feedback
benadida Feb 8, 2026
bc339e2
feat(booth2026): enhance accessibility with skip links and focus mana…
benadida Feb 8, 2026
89f1f06
feat(booth2026): add error handling and loading states
benadida Feb 8, 2026
16335c5
feat(booth2026): add responsive CSS and visual polish
benadida Feb 8, 2026
e051d86
feat(booth2026): configure Vite for production build
benadida Feb 8, 2026
c5e8f30
docs(booth2026): add production deployment comment to URL config
benadida Feb 8, 2026
952cee0
feat(booth2026): move GIF assets to public directory for production b…
benadida Feb 8, 2026
8309c0c
fix(booth2026): address all code review issues
benadida Feb 8, 2026
d4ca879
fix: address code review feedback for Helios Booth 2026
benadida Feb 8, 2026
79119bd
fix(booth2026): serve built dist/ files via Django and copy lib/ into…
benadida Feb 8, 2026
b57e6b9
build: add root package.json for Heroku Node.js buildpack
benadida Feb 8, 2026
9b41f38
fix(booth2026): move build tools to dependencies for Heroku
benadida Feb 8, 2026
97cd547
fix(booth2026): fix form submission and port single ballot verifier
benadida Feb 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,075 changes: 1,075 additions & 0 deletions docs/implementation-plans/2026-01-18-helios-booth-lit-redesign/phase_01.md

Large diffs are not rendered by default.

Large diffs are not rendered by default.

1,519 changes: 1,519 additions & 0 deletions docs/implementation-plans/2026-01-18-helios-booth-lit-redesign/phase_03.md

Large diffs are not rendered by default.

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions heliosbooth2026/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
dist/
45 changes: 45 additions & 0 deletions heliosbooth2026/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Helios Voting Booth - Secure online voting with cryptographic verification">
<meta name="robots" content="noindex, nofollow">
<title>Helios Voting Booth</title>

<!-- Styles -->
<link rel="stylesheet" href="./src/styles/booth.css">

<!-- Crypto libraries must load before the app (synchronous) -->
<script src="lib/jscrypto/jsbn.js"></script>
<script src="lib/jscrypto/jsbn2.js"></script>
<script src="lib/jscrypto/sjcl.js"></script>
<script src="lib/jscrypto/class.js"></script>
<script src="lib/jscrypto/bigint.js"></script>
<script src="lib/jscrypto/random.js"></script>
<script src="lib/jscrypto/elgamal.js"></script>
<script src="lib/jscrypto/sha1.js"></script>
<script src="lib/jscrypto/sha2.js"></script>
<script src="lib/jscrypto/helios.js"></script>
<script src="lib/underscore-min.js"></script>

<!-- Noscript fallback -->
<noscript>
<style>
booth-app { display: none; }
.noscript-message { display: block !important; }
</style>
</noscript>
</head>
<body>
<div class="noscript-message" style="display: none; padding: 20px; text-align: center;">
<h1>JavaScript Required</h1>
<p>The Helios Voting Booth requires JavaScript to encrypt your ballot securely.</p>
<p>Please enable JavaScript in your browser settings and reload this page.</p>
</div>

<booth-app></booth-app>

<script type="module" src="./src/main.ts"></script>
</body>
Comment on lines +10 to +44
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This entrypoint loads ./src/main.ts. That works under the Vite dev server, but if index.html is served directly by Django/static hosting (as implied by the new /booth2026/ route), browsers won’t execute TypeScript. For non-Vite environments you likely need to serve the Vite-built dist/index.html (which references compiled JS) instead of this source index.html.

Copilot uses AI. Check for mistakes.
</html>
5 changes: 5 additions & 0 deletions heliosbooth2026/lib/jscrypto/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

JavaScript crypto.

IMPORTANT: this library REQUIRES that a variable JSCRYPTO_HOME be set by an HTML file, indicating
the complete path to the current directory
Binary file added heliosbooth2026/lib/jscrypto/bigint.class
Binary file not shown.
85 changes: 85 additions & 0 deletions heliosbooth2026/lib/jscrypto/bigint.dummy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* A dummy version of bigint for Helios
*
* no math is done in JavaScript, but the BigInt abstraction exists so that
* higher-level data structures can be parsed/serialized appropriately.
*/

// A wrapper for java.math.BigInteger with some appropriate extra functions for JSON and
// generally being a nice JavaScript object.

BigIntDummy = Class.extend({
init: function(value, radix) {
if (radix != 10)
throw "in dummy, only radix=10, here radix=" + radix;

this.value = value;
},

toString: function() {
return this.value;
},

toJSONObject: function() {
// toString is apparently not overridden in IE, so we reproduce it here.
return this.value;
},

add: function(other) {
throw "dummy, no add!";
},

bitLength: function() {
throw "dummy, nobitlength!";
},

mod: function(modulus) {
throw "dummy, no mod!";
},

equals: function(other) {
throw "dummy, no equals!";
},

modPow: function(exp, modulus) {
throw "dummy, no modpow!";
},

negate: function() {
throw "dummy, no negate!";
},

multiply: function(other) {
throw "dummy, no multiply!";
},

modInverse: function(modulus) {
throw "dummy, no modInverse";
}

});

BigIntDummy.use_applet = false;

BigIntDummy.is_dummy = true;
BigIntDummy.in_browser = false;

BigIntDummy.fromJSONObject = function(s) {
return new BigIntDummy(s, 10);
};

BigIntDummy.fromInt = function(i) {
return BigIntDummy.fromJSONObject("" + i);
};

BigIntDummy.ZERO = new BigIntDummy("0",10);
BigIntDummy.ONE = new BigIntDummy("1",10);
BigIntDummy.TWO = new BigIntDummy("2",10);
BigIntDummy.FORTY_TWO = new BigIntDummy("42",10);

BigIntDummy.ready_p = true;

BigIntDummy.setup = function(callback, fail_callback) {
//console.log("using dummy bigint");
callback();
}
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid automated semicolon insertion (91% of all statements in the enclosing script have an explicit semicolon).

Suggested change
}
};

Copilot uses AI. Check for mistakes.
25 changes: 25 additions & 0 deletions heliosbooth2026/lib/jscrypto/bigint.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

/*
* A simple applet for generating Bigints from JavaScript
*
* inspired from Stanford's SRP, and extended for Prime Number generation.
*/

public class bigint extends java.applet.Applet {
public java.security.SecureRandom newSecureRandom() {
return new java.security.SecureRandom();
}

public java.math.BigInteger newBigInteger(String value, int radix) {
return new java.math.BigInteger(value, radix);
}

public java.math.BigInteger randomBigInteger(int bitlen, java.util.Random rng) {
return new java.math.BigInteger(bitlen, rng);
}

public java.math.BigInteger randomPrimeBigInteger(int bitlen, int certainty, java.util.Random rng) {
return new java.math.BigInteger(bitlen, certainty, rng);
}
}

207 changes: 207 additions & 0 deletions heliosbooth2026/lib/jscrypto/bigint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
/*
* This software incorporates components derived from the
* Secure Remote Password JavaScript demo developed by
* Tom Wu (tjw@CS.Stanford.EDU).
*
* This library is almost entirely re-written by Ben Adida (ben@adida.net)
* with a BigInt wrapper.
*
* IMPORTANT: this library REQUIRES that a variable JSCRYPTO_HOME be set by an HTML file, indicating
* the complete path to the current directory
*/

// A wrapper for java.math.BigInteger with some appropriate extra functions for JSON and
// generally being a nice JavaScript object.

// let's try always using SJCL
var USE_SJCL = true;

// let's make this much cleaner
if (USE_SJCL) {
// why not?
var BigInt = BigInteger;
// ZERO AND ONE are already taken care of
BigInt.TWO = new BigInt("2",10);

BigInt.setup = function(callback, fail_callback) {
// nothing to do but go
callback();
}

BigInt.prototype.toJSONObject = function() {
return this.toString();
};

} else {
BigInt = Class.extend({
init: function(value, radix) {
if (value == null) {
throw "null value!";
}

if (USE_SJCL) {
this._java_bigint = new BigInteger(value, radix);
} else if (BigInt.use_applet) {
this._java_bigint = BigInt.APPLET.newBigInteger(value, radix);
} else {
try {
this._java_bigint = new java.math.BigInteger(value, radix);
} catch (e) {
// alert("oy " + e.toString() + " value=" + value + " , radix=" + radix);
throw TypeError
}
}
},

toString: function() {
return this._java_bigint.toString() + "";
},

toJSONObject: function() {
return this.toString();
},

add: function(other) {
return BigInt._from_java_object(this._java_bigint.add(other._java_bigint));
},

bitLength: function() {
return this._java_bigint.bitLength();
},

mod: function(modulus) {
return BigInt._from_java_object(this._java_bigint.mod(modulus._java_bigint));
},

equals: function(other) {
return this._java_bigint.equals(other._java_bigint);
},

modPow: function(exp, modulus) {
return BigInt._from_java_object(this._java_bigint.modPow(exp._java_bigint, modulus._java_bigint));
},

negate: function() {
return BigInt._from_java_object(this._java_bigint.negate());
},

multiply: function(other) {
return BigInt._from_java_object(this._java_bigint.multiply(other._java_bigint));
},

modInverse: function(modulus) {
return BigInt._from_java_object(this._java_bigint.modInverse(modulus._java_bigint));
}

});
Comment on lines +16 to +96
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This use of variable 'USE_SJCL' always evaluates to true.

Suggested change
// let's try always using SJCL
var USE_SJCL = true;
// let's make this much cleaner
if (USE_SJCL) {
// why not?
var BigInt = BigInteger;
// ZERO AND ONE are already taken care of
BigInt.TWO = new BigInt("2",10);
BigInt.setup = function(callback, fail_callback) {
// nothing to do but go
callback();
}
BigInt.prototype.toJSONObject = function() {
return this.toString();
};
} else {
BigInt = Class.extend({
init: function(value, radix) {
if (value == null) {
throw "null value!";
}
if (USE_SJCL) {
this._java_bigint = new BigInteger(value, radix);
} else if (BigInt.use_applet) {
this._java_bigint = BigInt.APPLET.newBigInteger(value, radix);
} else {
try {
this._java_bigint = new java.math.BigInteger(value, radix);
} catch (e) {
// alert("oy " + e.toString() + " value=" + value + " , radix=" + radix);
throw TypeError
}
}
},
toString: function() {
return this._java_bigint.toString() + "";
},
toJSONObject: function() {
return this.toString();
},
add: function(other) {
return BigInt._from_java_object(this._java_bigint.add(other._java_bigint));
},
bitLength: function() {
return this._java_bigint.bitLength();
},
mod: function(modulus) {
return BigInt._from_java_object(this._java_bigint.mod(modulus._java_bigint));
},
equals: function(other) {
return this._java_bigint.equals(other._java_bigint);
},
modPow: function(exp, modulus) {
return BigInt._from_java_object(this._java_bigint.modPow(exp._java_bigint, modulus._java_bigint));
},
negate: function() {
return BigInt._from_java_object(this._java_bigint.negate());
},
multiply: function(other) {
return BigInt._from_java_object(this._java_bigint.multiply(other._java_bigint));
},
modInverse: function(modulus) {
return BigInt._from_java_object(this._java_bigint.modInverse(modulus._java_bigint));
}
});
// Using SJCL-backed BigInteger implementation directly.
// NOTE: previously this was controlled by a USE_SJCL flag that was always true.
// let's make this much cleaner
// why not?
var BigInt = BigInteger;
// ZERO AND ONE are already taken care of
BigInt.TWO = new BigInt("2",10);
BigInt.setup = function(callback, fail_callback) {
// nothing to do but go
callback();
};
BigInt.prototype.toJSONObject = function() {
return this.toString();
};

Copilot uses AI. Check for mistakes.

BigInt.ready_p = false;

//
// Some Class Methods
//
BigInt._from_java_object = function(jo) {
// bogus object
var obj = new BigInt("0",10);
obj._java_bigint = jo;
return obj;
};

//
// do the applet check
//
function check_applet() {
/* Is this Netscape 4.xx? */
var is_ns4 = (navigator.appName == "Netscape" && navigator.appVersion < "5");

/* Do we need the toString() workaround (requires applet)? */
var str_workaround = (navigator.appName == "Opera");

// stuff this in BigInt
BigInt.is_ie = (navigator.appName == "Microsoft Internet Explorer");

/* Decide whether we need the helper applet or not */
var use_applet = BigInt.is_ie || (!is_ns4 && navigator.platform.substr(0, 5) == "Linux") || str_workaround || typeof(java) == 'undefined';

Comment on lines +113 to +125
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused function check_applet.

Suggested change
function check_applet() {
/* Is this Netscape 4.xx? */
var is_ns4 = (navigator.appName == "Netscape" && navigator.appVersion < "5");
/* Do we need the toString() workaround (requires applet)? */
var str_workaround = (navigator.appName == "Opera");
// stuff this in BigInt
BigInt.is_ie = (navigator.appName == "Microsoft Internet Explorer");
/* Decide whether we need the helper applet or not */
var use_applet = BigInt.is_ie || (!is_ns4 && navigator.platform.substr(0, 5) == "Linux") || str_workaround || typeof(java) == 'undefined';

Copilot uses AI. Check for mistakes.
// add the applet
if (use_applet) {
var applet_base = JSCRYPTO_HOME;

var applet_html = '<applet codebase="' + applet_base + '" mayscript name="bigint" code="bigint.class" width=1 height=1 id="bigint_applet"></applet>';
// var applet_html = '<object classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93" name="bigint" width="1" height="1" codebase="http://java.sun.com/products/plugin/autodl/jinstall-1_5_0-windows-i586.cab#Version=1,5,0,0"> <param name="code" value="bigint.class"> <param name="codebase" value="' + applet_base + '"> <param name="archive" value="myapplet.jar"> <param name="type" value="application/x-java-applet;version=1.5.0"> <param name="scriptable" value="true"> <param name="mayscript" value="false"> <comment> <embed code="bigint.class" name="bigint" java_codebase="' + applet_base + '" width="1" height="1" scriptable="true" mayscript="false" type="application/x-java-applet;version=1.5.0" pluginspage="http://java.sun.com/j2se/1.5.0/download.html"> <noembed>No Java Support.</noembed> </embed> </comment> </object>';
$("#applet_div").html(applet_html);
}

return use_applet;
};

// Set up the pointer to the applet if necessary, and some
// basic Big Ints that everyone needs (0, 1, 2, and 42)
BigInt._setup = function() {
if (BigInt.use_applet) {
BigInt.APPLET = document.applets["bigint"];
}

try {
BigInt.ZERO = new BigInt("0",10);
BigInt.ONE = new BigInt("1",10);
BigInt.TWO = new BigInt("2",10);
BigInt.FORTY_TWO = new BigInt("42",10);

BigInt.ready_p = true;
} catch (e) {
// not ready
// count how many times we've tried
if (this.num_invocations == null)
this.num_invocations = 0;

this.num_invocations += 1;

if (this.num_invocations > 5) {
// try SJCL
if (!USE_SJCL) {
USE_SJCL = true;
this.num_invocations = 1;
BigInt.use_applet = false;
} else {

if (BigInt.setup_interval)
window.clearInterval(BigInt.setup_interval);

if (BigInt.setup_fail) {
BigInt.setup_fail();
} else {
alert('bigint failed!');
}
}
}
return;
}

if (BigInt.setup_interval)
window.clearInterval(BigInt.setup_interval);

if (BigInt.setup_callback)
BigInt.setup_callback();
};

BigInt.setup = function(callback, fail_callback) {
if (callback)
BigInt.setup_callback = callback;

if (fail_callback)
BigInt.setup_fail = fail_callback;

BigInt.setup_interval = window.setInterval("BigInt._setup()", 1000);
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid using functions that evaluate strings as code.

Suggested change
BigInt.setup_interval = window.setInterval("BigInt._setup()", 1000);
BigInt.setup_interval = window.setInterval(function() { BigInt._setup(); }, 1000);

Copilot uses AI. Check for mistakes.
}
}

BigInt.fromJSONObject = function(s) {
return new BigInt(s, 10);
};

BigInt.fromInt = function(i) {
return BigInt.fromJSONObject("" + i);
};

BigInt.use_applet = false;
Loading
Loading