<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>Crypto Island</title>
    <link href="https://cryptoisland.blog/atom.xml" rel="self" />
    <link href="https://cryptoisland.blog" />
    <id>https://cryptoisland.blog/atom.xml</id>
    <author>
        <name>jefdaj</name>
        
        <email>jefdaj@protonmail.ch</email>
        
    </author>
    <updated>2025-12-23T00:00:00Z</updated>
    <entry>
    <title>ElectionGuard + Cardano Dev Update #3: Election Tests</title>
    <link href="https://cryptoisland.blog/posts/2025/12/23/egc-dev03-election-tests" />
    <id>https://cryptoisland.blog/posts/2025/12/23/egc-dev03-election-tests</id>
    <published>2025-12-23T00:00:00Z</published>
    <updated>2025-12-23T00:00:00Z</updated>
    <summary type="html"><![CDATA[
<div class="toc"><div class="header">Contents</div>
<ul>
<li><a href="#how-to-run-elections" id="toc-how-to-run-elections">How to run elections</a>
<ul>
<li><a href="#single-election" id="toc-single-election">Single Election</a></li>
<li><a href="#test-suite" id="toc-test-suite">Test Suite</a></li>
</ul></li>
<li><a href="#whats-tested" id="toc-whats-tested">What’s tested?</a>
<ul>
<li><a href="#honest-runs" id="toc-honest-runs">Honest Runs</a></li>
<li><a href="#attack-runs" id="toc-attack-runs">Attack Runs</a></li>
<li><a href="#example-mutate-attack" id="toc-example-mutate-attack">Example “mutate” attack</a></li>
</ul></li>
<li><a href="#whats-not-tested" id="toc-whats-not-tested">What’s <em>not</em> tested?</a></li>
</ul>
<center class="reminder"><img src="ogre.png"></img></center></div>
<p>This is dev update #3 for <a href="https://milestones.projectcatalyst.io/projects/1300090">my fund13 project</a>.
You can find the code <a href="https://github.com/jefdaj/electionguard-cardano/tree/trunk/milestone1/tests">here</a>, and a companion YouTube video <a href="https://www.youtube.com/watch?v=BuqPbmf5Ko4">here</a>.</p>
<p>Today I’m excited to show a little more systematic suite of tests to verify that
the elections I’m running work with a variety of parameters, and
can withstand some hypothetical attacks. This isn’t meant to be a comprehensive
security audit or cover the game theory or anything like that; it’s mainly
meant to catch straightforward coding errors.</p>
<h1 id="how-to-run-elections">How to run elections</h1>
<p>I plan to make this part of the main codebase going forward,
so I kept the single election entrypoint and added a new one for tests.</p>
<h2 id="single-election">Single Election</h2>
<p>Edit <a href="https://github.com/jefdaj/electionguard-cardano/tree/trunk/milestone1/tests/election.json">election.json</a> and run <a href="https://github.com/jefdaj/electionguard-cardano/tree/trunk/milestone1/tests/election.sh">election.sh</a> to try things.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="bu">cd</span> electionguard-cardano/milestone1/tests</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="ex">nix</span> develop</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="ex">./election.sh</span></span></code></pre></div>
<p>There’s one new <code>attacks</code> field, which we’ll get to below.
The <a href="/posts/2025/12/23/egc-dev01-election-demo/#dev-options">old dev options</a> also still work.</p>
<p>The other change is that the admin, guardians, and independent verifiers automatically do their own verifications now. The admin’s takes the place of the separate official summary JSON I used in the first version.</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> tree data/public/4_verify</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="ex">data/public/4_verify</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> admin_1.json</span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> guardian_1.json</span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> guardian_2.json</span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> guardian_3.json</span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a><span class="ex">└──</span> verifier_1.json</span></code></pre></div>
<h2 id="test-suite">Test Suite</h2>
<p>More interestingly, <a href="https://github.com/jefdaj/electionguard-cardano/tree/trunk/milestone1/tests/test.sh">test.sh</a> uses <a href="https://hypothesis.readthedocs.io/en/latest/">Hypothesis</a> to generate
arbitrary election configs, runs the corresponding test elections, and verifies
some properties of the output files. The elections can be slow (2.5 hours to
run 143 of them on my laptop), but it caches the output files so at least
editing and re-running the property tests is fast.</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="bu">cd</span> electionguard-cardano/milestone1/tests</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="ex">nix</span> develop</span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="ex">./test.sh</span></span></code></pre></div>
<div class="sourceCode" id="cb4"><pre class="sourceCode txt"><code class="sourceCode default"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a>============================= test session starts ==============================</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>platform linux -- Python 3.12.11, pytest-8.3.5, pluggy-1.5.0 -- ...</span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>... collected 51 items</span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>election.py::test_json_voteconfig &lt;- config.py PASSED                    [  1%]</span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a>election.py::test_json_contestconfig &lt;- config.py PASSED                 [  3%]</span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a>election.py::test_json_electionconfig &lt;- config.py PASSED                [  5%]</span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a>election.py::test_json_attackcfg &lt;- config.py PASSED                     [  7%]</span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a>election.py::test_json_honestrun &lt;- config.py PASSED                     [  9%]</span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a>election.py::test_json_attackrun &lt;- config.py PASSED                     [ 11%]</span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_always_verified PASSED                          [ 13%]</span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_all_verifiers_agree_exactly PASSED              [ 15%]</span>
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_n_verifications_matches_cfg PASSED              [ 17%]</span>
<span id="cb4-14"><a href="#cb4-14" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_cast_votes_match_config PASSED                  [ 19%]</span>
<span id="cb4-15"><a href="#cb4-15" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_spoiled_votes_match_config PASSED               [ 21%]</span>
<span id="cb4-16"><a href="#cb4-16" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_manifest_verified PASSED                        [ 23%]</span>
<span id="cb4-17"><a href="#cb4-17" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_ceremony_details_verified PASSED                [ 25%]</span>
<span id="cb4-18"><a href="#cb4-18" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_gather_announce_verified PASSED                 [ 27%]</span>
<span id="cb4-19"><a href="#cb4-19" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_all_guardian_backups_verified PASSED            [ 29%]</span>
<span id="cb4-20"><a href="#cb4-20" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_all_guardian_verifications_verified PASSED      [ 31%]</span>
<span id="cb4-21"><a href="#cb4-21" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_gather_ceremony_verified PASSED                 [ 33%]</span>
<span id="cb4-22"><a href="#cb4-22" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_joint_key_verified PASSED                       [ 35%]</span>
<span id="cb4-23"><a href="#cb4-23" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_build_election_verified PASSED                  [ 37%]</span>
<span id="cb4-24"><a href="#cb4-24" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_constants_verified PASSED                       [ 39%]</span>
<span id="cb4-25"><a href="#cb4-25" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_context_verified PASSED                         [ 41%]</span>
<span id="cb4-26"><a href="#cb4-26" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_gather_constants_verified PASSED                [ 43%]</span>
<span id="cb4-27"><a href="#cb4-27" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_all_devices_verified PASSED                     [ 45%]</span>
<span id="cb4-28"><a href="#cb4-28" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_gather_config_verified PASSED                   [ 47%]</span>
<span id="cb4-29"><a href="#cb4-29" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_all_ballots_submitted_verified PASSED           [ 49%]</span>
<span id="cb4-30"><a href="#cb4-30" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_all_ballots_cast_verified PASSED                [ 50%]</span>
<span id="cb4-31"><a href="#cb4-31" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_all_ballots_spoiled_verified PASSED             [ 52%]</span>
<span id="cb4-32"><a href="#cb4-32" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_all_spoiled_results_verified PASSED             [ 54%]</span>
<span id="cb4-33"><a href="#cb4-33" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_n_spoiled_decrypted_verified PASSED             [ 56%]</span>
<span id="cb4-34"><a href="#cb4-34" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_n_cast_spoiled_submitted_verified PASSED        [ 58%]</span>
<span id="cb4-35"><a href="#cb4-35" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_set_spoiled_decrypted_verified PASSED           [ 60%]</span>
<span id="cb4-36"><a href="#cb4-36" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_set_cast_spoiled_submitted_verified PASSED      [ 62%]</span>
<span id="cb4-37"><a href="#cb4-37" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_ballot_sets_verified PASSED                     [ 64%]</span>
<span id="cb4-38"><a href="#cb4-38" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_ciphertext_tally_verified PASSED                [ 66%]</span>
<span id="cb4-39"><a href="#cb4-39" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_tally_aggregation_verified PASSED               [ 68%]</span>
<span id="cb4-40"><a href="#cb4-40" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_plaintext_tally_verified PASSED                 [ 70%]</span>
<span id="cb4-41"><a href="#cb4-41" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_tally_decryption_verified PASSED                [ 72%]</span>
<span id="cb4-42"><a href="#cb4-42" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_gather_tally_verified PASSED                    [ 74%]</span>
<span id="cb4-43"><a href="#cb4-43" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_gather_decryptions_verified PASSED              [ 76%]</span>
<span id="cb4-44"><a href="#cb4-44" aria-hidden="true" tabindex="-1"></a>election.py::test_honest_gather_election_verified PASSED                 [ 78%]</span>
<span id="cb4-45"><a href="#cb4-45" aria-hidden="true" tabindex="-1"></a>election.py::test_attack_admin_withhold_manifest PASSED                  [ 80%]</span>
<span id="cb4-46"><a href="#cb4-46" aria-hidden="true" tabindex="-1"></a>election.py::test_attack_admin_ghost_after_vote PASSED                   [ 82%]</span>
<span id="cb4-47"><a href="#cb4-47" aria-hidden="true" tabindex="-1"></a>election.py::test_attack_device_withhold_submitted_ballot PASSED         [ 84%]</span>
<span id="cb4-48"><a href="#cb4-48" aria-hidden="true" tabindex="-1"></a>election.py::test_attack_device_withhold_cast_ballot PASSED              [ 86%]</span>
<span id="cb4-49"><a href="#cb4-49" aria-hidden="true" tabindex="-1"></a>election.py::test_attack_device_withhold_spoiled_ballot PASSED           [ 88%]</span>
<span id="cb4-50"><a href="#cb4-50" aria-hidden="true" tabindex="-1"></a>election.py::test_attack_device_mutate_submitted_ballot PASSED           [ 90%]</span>
<span id="cb4-51"><a href="#cb4-51" aria-hidden="true" tabindex="-1"></a>election.py::test_attack_device_mutate_spoiled_ballot PASSED             [ 92%]</span>
<span id="cb4-52"><a href="#cb4-52" aria-hidden="true" tabindex="-1"></a>election.py::test_attack_guardian_withhold_tally_share PASSED            [ 94%]</span>
<span id="cb4-53"><a href="#cb4-53" aria-hidden="true" tabindex="-1"></a>election.py::test_attack_guardian_withhold_spoiled_share PASSED          [ 96%]</span>
<span id="cb4-54"><a href="#cb4-54" aria-hidden="true" tabindex="-1"></a>election.py::test_verifiers_notice_attacks PASSED                        [ 98%]</span>
<span id="cb4-55"><a href="#cb4-55" aria-hidden="true" tabindex="-1"></a>election.py::test_attacks_are_logged PASSED                              [100%]</span>
<span id="cb4-56"><a href="#cb4-56" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-57"><a href="#cb4-57" aria-hidden="true" tabindex="-1"></a>======================= 51 passed in 9118.83s (2:31:58) ========================</span></code></pre></div>
<div class="sourceCode" id="cb5"><pre class="sourceCode txt"><code class="sourceCode default"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a>$ tree -L 2 tests</span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a>tests</span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a>├── test01ac1</span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a>│   ├── data</span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a>│   ├── election.json</span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a>│   └── election.log</span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a>├── test02b86</span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a>│   ├── data</span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a>│   ├── election.json</span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a>│   └── election.log</span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a>├── ...</span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a>└── testffd9e</span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a>    ├── data</span>
<span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a>    ├── election.json</span>
<span id="cb5-15"><a href="#cb5-15" aria-hidden="true" tabindex="-1"></a>    └── election.log</span>
<span id="cb5-16"><a href="#cb5-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-17"><a href="#cb5-17" aria-hidden="true" tabindex="-1"></a>287 directories, 286 files</span></code></pre></div>
<p>As you can see, each test is like the single elections we’ve been looking at so far.
They’re deterministic (random seeds based on config file hashes), so you can delete one and re-run the tests to have the same config recreated and run again.</p>
<p><em>Side note: the tests could probably be sped up significantly by running them in parallel.
I didn’t do that yet because I wanted to make sure they all pass before introducing any potential issues related to interactions between Docker networks.</em></p>
<h1 id="whats-tested">What’s tested?</h1>
<p>Besides some small details like round-tripping config files, the main things are:</p>
<ul>
<li>for any honest (non-attacked) election, the verifiers should verify a bunch of properties</li>
<li>for any attacked election, the verifiers should fail to certify <code>gather_election</code></li>
<li>for specific types of attacks, there are specific additional DAG nodes that should fail to verify</li>
<li>some attacks cause certain phases of the election to be skipped, but verifiers should always run at the end</li>
</ul>
<h2 id="honest-runs">Honest Runs</h2>
<p>The most important properties being verified here are probably that the verifiers always certify honest elections, and the final tally always equals the cast votes from the JSON config.</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="at">@given_honest_election</span>()</span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> test_honest_gather_election_verified(testdir: ElectionTestDir):</span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a>  assert_verifiers_verified(testdir, <span class="st">&#39;gather_election&#39;</span>)</span></code></pre></div>
<div class="sourceCode" id="cb7"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="at">@given_honest_election</span>()</span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> test_honest_cast_votes_match_config(testdir: ElectionTestDir):</span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a>    cfg <span class="op">=</span> load_config_json(testdir)</span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a>    expected_cast_totals <span class="op">=</span> cast_vote_totals_from_config(cfg)</span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a>    <span class="co"># admin isn&#39;t special here; could use any verifier</span></span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a>    summary <span class="op">=</span> load_summary_json(testdir, <span class="st">&#39;admin_1&#39;</span>)</span>
<span id="cb7-9"><a href="#cb7-9" aria-hidden="true" tabindex="-1"></a>    actual_cast_totals <span class="op">=</span> <span class="bu">sorted</span>(summary[<span class="st">&#39;Final tally of cast ballots&#39;</span>])</span>
<span id="cb7-10"><a href="#cb7-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-11"><a href="#cb7-11" aria-hidden="true" tabindex="-1"></a>    <span class="cf">assert</span> <span class="bu">len</span>(expected_cast_totals) <span class="op">==</span> <span class="bu">len</span>(actual_cast_totals)</span>
<span id="cb7-12"><a href="#cb7-12" aria-hidden="true" tabindex="-1"></a>    <span class="cf">for</span> (expected, actual) <span class="kw">in</span> <span class="bu">zip</span>(expected_cast_totals, actual_cast_totals):</span>
<span id="cb7-13"><a href="#cb7-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-14"><a href="#cb7-14" aria-hidden="true" tabindex="-1"></a>        <span class="cf">assert</span> <span class="bu">set</span>(expected[<span class="st">&#39;answers&#39;</span>].keys()) <span class="op">==</span> <span class="bu">set</span>(actual[<span class="st">&#39;answers&#39;</span>].keys())</span>
<span id="cb7-15"><a href="#cb7-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-16"><a href="#cb7-16" aria-hidden="true" tabindex="-1"></a>        <span class="cf">for</span> (answer, n_actual) <span class="kw">in</span> actual[<span class="st">&#39;answers&#39;</span>].items():</span>
<span id="cb7-17"><a href="#cb7-17" aria-hidden="true" tabindex="-1"></a>            <span class="cf">assert</span> n_actual <span class="op">==</span> expected[<span class="st">&#39;answers&#39;</span>][answer]</span></code></pre></div>
<h2 id="attack-runs">Attack Runs</h2>
<p>Just as import important, and probably more fun, are the attack tests! Here are the ones I’ve written so far:</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a>ATTACKS <span class="op">=</span> { </span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a>    <span class="st">&#39;admin_ghost_after_vote&#39;</span>          : {<span class="st">&#39;who&#39;</span>: <span class="st">&#39;admin&#39;</span>, <span class="st">&#39;when&#39;</span>: [<span class="st">&#39;tally&#39;</span>, <span class="st">&#39;decrypt_results&#39;</span>]},</span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a>    <span class="st">&#39;admin_withhold_manifest&#39;</span>         : {<span class="st">&#39;who&#39;</span>: <span class="st">&#39;admin&#39;</span>, <span class="st">&#39;when&#39;</span>: [<span class="st">&#39;build_manifest&#39;</span>]},</span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a>    <span class="st">&#39;device_mutate_spoiled_ballot&#39;</span>    : {<span class="st">&#39;who&#39;</span>: <span class="st">&#39;device&#39;</span>, <span class="st">&#39;when&#39;</span>: [<span class="st">&#39;vote_reveal_all&#39;</span>]},</span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a>    <span class="st">&#39;device_mutate_submitted_ballot&#39;</span>  : {<span class="st">&#39;who&#39;</span>: <span class="st">&#39;device&#39;</span>, <span class="st">&#39;when&#39;</span>: [<span class="st">&#39;vote_commit_all&#39;</span>]},</span>
<span id="cb8-6"><a href="#cb8-6" aria-hidden="true" tabindex="-1"></a>    <span class="st">&#39;device_withhold_cast_ballot&#39;</span>     : {<span class="st">&#39;who&#39;</span>: <span class="st">&#39;device&#39;</span>, <span class="st">&#39;when&#39;</span>: [<span class="st">&#39;vote_reveal_all&#39;</span>]},</span>
<span id="cb8-7"><a href="#cb8-7" aria-hidden="true" tabindex="-1"></a>    <span class="st">&#39;device_withhold_spoiled_ballot&#39;</span>  : {<span class="st">&#39;who&#39;</span>: <span class="st">&#39;device&#39;</span>, <span class="st">&#39;when&#39;</span>: [<span class="st">&#39;vote_reveal_all&#39;</span>]},</span>
<span id="cb8-8"><a href="#cb8-8" aria-hidden="true" tabindex="-1"></a>    <span class="st">&#39;device_withhold_submitted_ballot&#39;</span>: {<span class="st">&#39;who&#39;</span>: <span class="st">&#39;device&#39;</span>, <span class="st">&#39;when&#39;</span>: [<span class="st">&#39;vote_commit_all&#39;</span>]},</span>
<span id="cb8-9"><a href="#cb8-9" aria-hidden="true" tabindex="-1"></a>    <span class="st">&#39;guardian_withhold_spoiled_share&#39;</span> : {<span class="st">&#39;who&#39;</span>: <span class="st">&#39;guardian&#39;</span>, <span class="st">&#39;when&#39;</span>: [<span class="st">&#39;decrypt_shares&#39;</span>]},</span>
<span id="cb8-10"><a href="#cb8-10" aria-hidden="true" tabindex="-1"></a>    <span class="st">&#39;guardian_withhold_tally_share&#39;</span>   : {<span class="st">&#39;who&#39;</span>: <span class="st">&#39;guardian&#39;</span>, <span class="st">&#39;when&#39;</span>: [<span class="st">&#39;decrypt_shares&#39;</span>]},</span>
<span id="cb8-11"><a href="#cb8-11" aria-hidden="true" tabindex="-1"></a>}</span></code></pre></div>
<p>The way they’re implemented is that in the main <code>election</code> function in [election.py][elpy],
I’ve added an optional attack after each step.</p>
<div class="sourceCode" id="cb9"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> election(cfg, log):</span>
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a>    <span class="cf">try</span>:</span>
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a>        build_manifest(cfg, log)        <span class="op">;</span> attack_all(cfg, log, <span class="st">&#39;build_manifest&#39;</span>)</span>
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a>        announce_key_ceremony(cfg, log) <span class="op">;</span> attack_all(cfg, log, <span class="st">&#39;announce_key_ceremony&#39;</span>)</span>
<span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a>        key_ceremony_round1(cfg, log)   <span class="op">;</span> attack_all(cfg, log, <span class="st">&#39;key_ceremony_round1&#39;</span>)</span>
<span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a>        key_ceremony_round2(cfg, log)   <span class="op">;</span> attack_all(cfg, log, <span class="st">&#39;key_ceremony_round2&#39;</span>)</span>
<span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a>        key_ceremony_round3(cfg, log)   <span class="op">;</span> attack_all(cfg, log, <span class="st">&#39;key_ceremony_round3&#39;</span>)</span>
<span id="cb9-8"><a href="#cb9-8" aria-hidden="true" tabindex="-1"></a>        publish_joint_key(cfg, log)     <span class="op">;</span> attack_all(cfg, log, <span class="st">&#39;publish_joint_key&#39;</span>)</span>
<span id="cb9-9"><a href="#cb9-9" aria-hidden="true" tabindex="-1"></a>        build_election(cfg, log)        <span class="op">;</span> attack_all(cfg, log, <span class="st">&#39;build_election&#39;</span>)</span>
<span id="cb9-10"><a href="#cb9-10" aria-hidden="true" tabindex="-1"></a>        add_devices(cfg, log)           <span class="op">;</span> attack_all(cfg, log, <span class="st">&#39;add_devices&#39;</span>)</span>
<span id="cb9-11"><a href="#cb9-11" aria-hidden="true" tabindex="-1"></a>        ids <span class="op">=</span> vote_commit_all(cfg, log) <span class="op">;</span> attack_all(cfg, log, <span class="st">&#39;vote_commit_all&#39;</span>)</span>
<span id="cb9-12"><a href="#cb9-12" aria-hidden="true" tabindex="-1"></a>        vote_reveal_all(cfg, log, ids)  <span class="op">;</span> attack_all(cfg, log, <span class="st">&#39;vote_reveal_all&#39;</span>)</span>
<span id="cb9-13"><a href="#cb9-13" aria-hidden="true" tabindex="-1"></a>        tally(cfg, log)                 <span class="op">;</span> attack_all(cfg, log, <span class="st">&#39;tally&#39;</span>)</span>
<span id="cb9-14"><a href="#cb9-14" aria-hidden="true" tabindex="-1"></a>        decrypt_shares(cfg, log)        <span class="op">;</span> attack_all(cfg, log, <span class="st">&#39;decrypt_shares&#39;</span>)</span>
<span id="cb9-15"><a href="#cb9-15" aria-hidden="true" tabindex="-1"></a>        decrypt_results(cfg, log)       <span class="op">;</span> attack_all(cfg, log, <span class="st">&#39;decrypt_results&#39;</span>)</span>
<span id="cb9-16"><a href="#cb9-16" aria-hidden="true" tabindex="-1"></a>    <span class="cf">except</span> <span class="pp">Exception</span> <span class="im">as</span> e:</span>
<span id="cb9-17"><a href="#cb9-17" aria-hidden="true" tabindex="-1"></a>        <span class="bu">print</span>(e)</span>
<span id="cb9-18"><a href="#cb9-18" aria-hidden="true" tabindex="-1"></a>    <span class="cf">finally</span>:</span>
<span id="cb9-19"><a href="#cb9-19" aria-hidden="true" tabindex="-1"></a>        verify(cfg, log) <span class="op">;</span> attack_all(cfg, log, <span class="st">&#39;verify&#39;</span>)</span></code></pre></div>
<p>Attacks are listed by name in <a href="https://github.com/jefdaj/electionguard-cardano/tree/trunk/milestone1/tests/election.json">election.json</a>. When <code>attack_all</code> is called with the relevant step,
it randomly corrupts one of the containers with the relevant role and has it run an attack function from <a href="https://github.com/jefdaj/electionguard-cardano/tree/trunk/milestone1/tests/scripts/attack.py">attack.py</a>. Then the rest of the election continues normally.</p>
<h2 id="example-mutate-attack">Example “mutate” attack</h2>
<p>Let’s look at one particular attack I think is cool, and that helps give some
confidence the cryptography is working as intended. Here’s the implementation.</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="co"># election.py</span></span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a><span class="at">@given_attacked_election</span>(<span class="st">&#39;device_mutate_submitted_ballot&#39;</span>, max_examples<span class="op">=</span><span class="dv">50</span>)</span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> test_attack_device_mutate_submitted_ballot(testdir: ElectionTestDir):</span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a>    assert_verifiers_reject(testdir, [</span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a>        <span class="st">&#39;all_ballots_submitted&#39;</span>,</span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-7"><a href="#cb10-7" aria-hidden="true" tabindex="-1"></a>        <span class="co"># The tally will still validate if the mutated ballot</span></span>
<span id="cb10-8"><a href="#cb10-8" aria-hidden="true" tabindex="-1"></a>        <span class="co"># was spoiled rather than cast, so this may work:</span></span>
<span id="cb10-9"><a href="#cb10-9" aria-hidden="true" tabindex="-1"></a>        <span class="co"># &#39;ciphertext_tally&#39;,</span></span>
<span id="cb10-10"><a href="#cb10-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-11"><a href="#cb10-11" aria-hidden="true" tabindex="-1"></a>        <span class="st">&#39;gather_election&#39;</span>,</span>
<span id="cb10-12"><a href="#cb10-12" aria-hidden="true" tabindex="-1"></a>    ])</span>
<span id="cb10-13"><a href="#cb10-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-14"><a href="#cb10-14" aria-hidden="true" tabindex="-1"></a><span class="co"># scripts/attack.py</span></span>
<span id="cb10-15"><a href="#cb10-15" aria-hidden="true" tabindex="-1"></a><span class="at">@announce_attack</span></span>
<span id="cb10-16"><a href="#cb10-16" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> device_mutate_submitted_ballot(log, pubdir, privdir, step):</span>
<span id="cb10-17"><a href="#cb10-17" aria-hidden="true" tabindex="-1"></a>    submitted_ballots <span class="op">=</span> <span class="bu">set</span>(d[<span class="st">&#39;ballot_id&#39;</span>] <span class="cf">for</span> d <span class="kw">in</span> list_submitted_ballot_fmtargs(pubdir))</span>
<span id="cb10-18"><a href="#cb10-18" aria-hidden="true" tabindex="-1"></a>    own_ballots       <span class="op">=</span> <span class="bu">set</span>(d[<span class="st">&#39;ballot_id&#39;</span>] <span class="cf">for</span> d <span class="kw">in</span> list_own_ballot_fmtargs(privdir))</span>
<span id="cb10-19"><a href="#cb10-19" aria-hidden="true" tabindex="-1"></a>    valid_choices <span class="op">=</span> [{<span class="st">&#39;ballot_id&#39;</span>: i} <span class="cf">for</span> i <span class="kw">in</span> own_ballots.intersection(submitted_ballots)]</span>
<span id="cb10-20"><a href="#cb10-20" aria-hidden="true" tabindex="-1"></a>    ballot_fmtargs <span class="op">=</span> random.choice(valid_choices)</span>
<span id="cb10-21"><a href="#cb10-21" aria-hidden="true" tabindex="-1"></a>    mutate_public_record_crypto_in_place(</span>
<span id="cb10-22"><a href="#cb10-22" aria-hidden="true" tabindex="-1"></a>        log, pubdir, <span class="st">&#39;ballot_submitted&#39;</span>,</span>
<span id="cb10-23"><a href="#cb10-23" aria-hidden="true" tabindex="-1"></a>        [</span>
<span id="cb10-24"><a href="#cb10-24" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-25"><a href="#cb10-25" aria-hidden="true" tabindex="-1"></a>            <span class="co"># These can be changed without verifiers noticing:</span></span>
<span id="cb10-26"><a href="#cb10-26" aria-hidden="true" tabindex="-1"></a>            <span class="co"># &#39;description_hash&#39;,</span></span>
<span id="cb10-27"><a href="#cb10-27" aria-hidden="true" tabindex="-1"></a>            <span class="co"># &#39;manifest_hash&#39;,</span></span>
<span id="cb10-28"><a href="#cb10-28" aria-hidden="true" tabindex="-1"></a>            <span class="co"># &#39;pad&#39;,</span></span>
<span id="cb10-29"><a href="#cb10-29" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-30"><a href="#cb10-30" aria-hidden="true" tabindex="-1"></a>            <span class="co"># These can&#39;t:</span></span>
<span id="cb10-31"><a href="#cb10-31" aria-hidden="true" tabindex="-1"></a>            <span class="st">&#39;challenge&#39;</span>,</span>
<span id="cb10-32"><a href="#cb10-32" aria-hidden="true" tabindex="-1"></a>            <span class="st">&#39;crypto_hash&#39;</span>,</span>
<span id="cb10-33"><a href="#cb10-33" aria-hidden="true" tabindex="-1"></a>            <span class="st">&#39;data&#39;</span></span>
<span id="cb10-34"><a href="#cb10-34" aria-hidden="true" tabindex="-1"></a>            <span class="st">&#39;proof_one_data&#39;</span>,</span>
<span id="cb10-35"><a href="#cb10-35" aria-hidden="true" tabindex="-1"></a>            <span class="st">&#39;proof_one_pad&#39;</span>,</span>
<span id="cb10-36"><a href="#cb10-36" aria-hidden="true" tabindex="-1"></a>            <span class="st">&#39;proof_one_response&#39;</span>,</span>
<span id="cb10-37"><a href="#cb10-37" aria-hidden="true" tabindex="-1"></a>            <span class="st">&#39;proof_zero_data&#39;</span>,</span>
<span id="cb10-38"><a href="#cb10-38" aria-hidden="true" tabindex="-1"></a>            <span class="st">&#39;proof_zero_pad&#39;</span>,</span>
<span id="cb10-39"><a href="#cb10-39" aria-hidden="true" tabindex="-1"></a>            <span class="st">&#39;proof_zero_response&#39;</span>,</span>
<span id="cb10-40"><a href="#cb10-40" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-41"><a href="#cb10-41" aria-hidden="true" tabindex="-1"></a>        ],</span>
<span id="cb10-42"><a href="#cb10-42" aria-hidden="true" tabindex="-1"></a>        <span class="op">**</span>ballot_fmtargs</span>
<span id="cb10-43"><a href="#cb10-43" aria-hidden="true" tabindex="-1"></a>    )</span></code></pre></div>
<p>Attack calls are logged in the main election log, and more detailed logs are
available in the private data of the corrupted container.</p>
<div class="sourceCode" id="cb11"><pre class="sourceCode txt"><code class="sourceCode default"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a># election.log</span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a>### attack ###</span>
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-4"><a href="#cb11-4" aria-hidden="true" tabindex="-1"></a>docker exec testbea22-device2-1 poetry run /scripts/attack.py attack --public-dir /data/public --private-dir /data/private --logfile /data/private/attack.log --attack-fn device_mutate_submitted_ballot --step vote_commit_all --random-seed 62058</span>
<span id="cb11-5"><a href="#cb11-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-6"><a href="#cb11-6" aria-hidden="true" tabindex="-1"></a># data/private/device_2/attack.log</span>
<span id="cb11-7"><a href="#cb11-7" aria-hidden="true" tabindex="-1"></a>### running device_mutate_submitted_ballot ###</span>
<span id="cb11-8"><a href="#cb11-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-9"><a href="#cb11-9" aria-hidden="true" tabindex="-1"></a>targeting /data/public/2_ballots/1_submitted/ballot-ba77bc54-df81-11f0-a9fc-32689d655a73.json</span>
<span id="cb11-10"><a href="#cb11-10" aria-hidden="true" tabindex="-1"></a>there are 31 matching keys</span>
<span id="cb11-11"><a href="#cb11-11" aria-hidden="true" tabindex="-1"></a>targeting match 6, proof_one_response</span>
<span id="cb11-12"><a href="#cb11-12" aria-hidden="true" tabindex="-1"></a>mutated char 22: 9 -&gt; 6</span>
<span id="cb11-13"><a href="#cb11-13" aria-hidden="true" tabindex="-1"></a>old: BA796CFF96207D51E84852955ED4719023799ED479F5F86271F6AC7E9696C87D</span>
<span id="cb11-14"><a href="#cb11-14" aria-hidden="true" tabindex="-1"></a>new: BA796CFF96207D51E84852655ED4719023799ED479F5F86271F6AC7E9696C87D</span>
<span id="cb11-15"><a href="#cb11-15" aria-hidden="true" tabindex="-1"></a>overwrote /data/public/2_ballots/1_submitted/ballot-ba77bc54-df81-11f0-a9fc-32689d655a73.json</span>
<span id="cb11-16"><a href="#cb11-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-17"><a href="#cb11-17" aria-hidden="true" tabindex="-1"></a>### finished device_mutate_submitted_ballot ###</span></code></pre></div>
<p>As you can see it targets a specific device, then a submitted ballot, then a crypto field within that ballot, and a character in that field. It changes that one character to a different random hex value and saves the file.</p>
<p>This is admittedly a biologist’s approach to cryptography, but hey it works!
Hypothesis generates up to 50 election configs with that attack in them, runs the elections, and asserts that the verifiers always catch the error.</p>
<p>They do, and they give a reassuringly reasonable (if long) error message:</p>
<div class="sourceCode" id="cb12"><pre class="sourceCode json"><code class="sourceCode json"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="fu">{</span>                                                                                                          </span>
<span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;ballot_submitted&quot;</span><span class="fu">:</span> <span class="fu">{</span>                                                                                              </span>
<span id="cb12-3"><a href="#cb12-3" aria-hidden="true" tabindex="-1"></a>      <span class="dt">&quot;ballot-ba77bc54-df81-11f0-a9fc-32689d655a73&quot;</span><span class="fu">:</span> <span class="st">&quot;chaum_pedersen.py.is_valid:#L114: found an invalid Disjunctive Ch</span></span>
<span id="cb12-4"><a href="#cb12-4" aria-hidden="true" tabindex="-1"></a><span class="st">aum-Pedersen proof: {&#39;in_bounds_alpha&#39;: True, &#39;in_bounds_beta&#39;: True, &#39;in_bounds_a0&#39;: True, &#39;in_bounds_b0&#39;: True, &#39;in_b</span></span>
<span id="cb12-5"><a href="#cb12-5" aria-hidden="true" tabindex="-1"></a><span class="st">ounds_a1&#39;: True, &#39;in_bounds_b1&#39;: True, &#39;in_bounds_c0&#39;: True, &#39;in_bounds_c1&#39;: True, &#39;in_bounds_v0&#39;: True, &#39;in_bounds_v1&#39;</span></span>
<span id="cb12-6"><a href="#cb12-6" aria-hidden="true" tabindex="-1"></a><span class="st">: True, &#39;consistent_c&#39;: True, &#39;consistent_gv0&#39;: True, &#39;consistent_gv1&#39;: False, &#39;consistent_kv0&#39;: True, &#39;consistent_gc1k</span></span>
<span id="cb12-7"><a href="#cb12-7" aria-hidden="true" tabindex="-1"></a><span class="st">v1&#39;: False, &#39;k&#39;: &#39;E02A3CB1BAD36433B3357541B946B2BA791428A0B870A0D4A048B64025B2BFD365C5C0874EF9A9DE65808BC9AB458A6815CD7</span></span>
<span id="cb12-8"><a href="#cb12-8" aria-hidden="true" tabindex="-1"></a><span class="st">1A9B2F290B486EBD141DEC7ED40647379B1A4431B12353E84F40D989AD1B127992E88FA8C273FF1706F4B736CDF45DFF70959F5CA4088256620E4AC</span></span>
<span id="cb12-9"><a href="#cb12-9" aria-hidden="true" tabindex="-1"></a><span class="st">BA3E62172B5F4F53FE8CFE9664F5C965887686C66F3CB679F7BF497EF025CCC4F49C56DEF9AC6DA73537D4C85ED35D1E1C3E6E30B7215CB2C207A90</span></span>
<span id="cb12-10"><a href="#cb12-10" aria-hidden="true" tabindex="-1"></a><span class="st">07178C1F1E20A58D466F53340E445220D034B612EDA7D37DEC72B6C4F63C2F775358B32AFC221366DFE256B2BA5EC1CEDDB183823329FF0CB9C68FD</span></span>
<span id="cb12-11"><a href="#cb12-11" aria-hidden="true" tabindex="-1"></a><span class="st">42B679FCD792411712E955B10BD6BA9AEEE3C6CB898075744AB70FDBD7241618EA6644BEF1E94BB10CB995F641915BE625330B29929BCF3B1C0010A</span></span>
<span id="cb12-12"><a href="#cb12-12" aria-hidden="true" tabindex="-1"></a><span class="st">527502469CF86475DFA1BF155ADC4D146E7ECF62691137329D5EADF4D6089327CAF5FEC77C9A122BB905D818418CEBB13102E5CD8823949775DADAA</span></span>
<span id="cb12-13"><a href="#cb12-13" aria-hidden="true" tabindex="-1"></a><span class="st">4A4715B19B749C43ECD7BD13A1432DBFC977CDBDE0595F1CC6F9098E0226000DDD2B035DA458D11C2899A8A9FE36B3066A347D971B15EB0CC045C4B</span></span>
<span id="cb12-14"><a href="#cb12-14" aria-hidden="true" tabindex="-1"></a><span class="st">E5727F29468FAD0467E6A7676C76A3A8220CD02E3A59013B31C22E8078F4638D59361DC726A312ECAC1D09F22874023E0C2EF7F52C2C51E460EE989</span></span>
<span id="cb12-15"><a href="#cb12-15" aria-hidden="true" tabindex="-1"></a><span class="st">C4E64E832271F32F06BB74BC5514B8B3163709E65029DA46CBCBEC0262768781B87D7F01843DBBFFA19C2E7BDE&#39;, &#39;proof&#39;: DisjunctiveChaumP</span></span>
<span id="cb12-16"><a href="#cb12-16" aria-hidden="true" tabindex="-1"></a><span class="st">edersenProof(proof_zero_pad=&#39;9994F4B2E5F5C6AA5A0EE7B5042C96FEA1231DC87A474D2A658FF8FFA01334F78688013A8D1F2E8D9B5F6B6201</span></span>
<span id="cb12-17"><a href="#cb12-17" aria-hidden="true" tabindex="-1"></a><span class="st">70D40A12B166B1E3F59227F9336B01B8E664E032088758295A89BDCDDF8B3029695742C881A0FF833181C06DD27671DF6FD21561DF97B06203D8F67</span></span>
<span id="cb12-18"><a href="#cb12-18" aria-hidden="true" tabindex="-1"></a><span class="st">04AD0DAFD82CA053E9882B67AA3752FCC45D79E87FC3F130410A5D66794695E3C67F7D4F82A816D3812BA0E780351900B525D94CDCA208861830987</span></span>
<span id="cb12-19"><a href="#cb12-19" aria-hidden="true" tabindex="-1"></a><span class="st">095E22F04E5035B05F65A31D66292682A1FD0EC61D257DD0A21E8846BAADCF6F7057AE06236B7881726D8FC4D833557D8154A00D5F7CDE21D843058</span></span>
<span id="cb12-20"><a href="#cb12-20" aria-hidden="true" tabindex="-1"></a><span class="st">65E1C8661D196EB32369137D0C2F149BF2C42AE1927DD43813BFAD71E0C8A1EF7BA058C5A4E47B42BD2E5C490ED2F090FFFFEC9755F4E4DED1FD428</span></span>
<span id="cb12-21"><a href="#cb12-21" aria-hidden="true" tabindex="-1"></a><span class="st">8AC797290701FDDE425E36AE2E116D30BF2B6389F25CBE7C7366C68F3E4260F09E31C5FDBA6CC4EF3BD80DF9970CC69E594C003D5403F6F351957D7</span></span>
<span id="cb12-22"><a href="#cb12-22" aria-hidden="true" tabindex="-1"></a><span class="st">2672A04DF01DA764EA51490329A3C330187DBAA67F33BD2F0F409DDD78E5E452E1FE34F1767021BD77FC1AC62C3A2F3CD1F54E263AA91363B2F2252</span></span>
<span id="cb12-23"><a href="#cb12-23" aria-hidden="true" tabindex="-1"></a><span class="st">40F1E3F317B87CBA9806E148E3AEA61910EF29AAD1A2992BB988F6C78E44BFB16277D3FD5A09F7F5A3C66E9F0E8F2965279128D647A6653D08020D9</span></span>
<span id="cb12-24"><a href="#cb12-24" aria-hidden="true" tabindex="-1"></a><span class="st">92083DDA80D9CE44276A9F457C25CA7AC8EBC894E4479540C81C540B95381E54A73D2F5BCE9C7E0DB68BD444835988759B8D3&#39;, proof_zero_data</span></span>
<span id="cb12-25"><a href="#cb12-25" aria-hidden="true" tabindex="-1"></a><span class="st">=&#39;ED6BE23226ED3B1B863CCC9D675A60E1C050700AAE44050197B0AE710A9C6CD99611F9AD2862D31D4CBDCA81361E29BA3A40C13A9383A7AD6BFDC</span></span>
<span id="cb12-26"><a href="#cb12-26" aria-hidden="true" tabindex="-1"></a><span class="st">7FAE9BAC9136E680AE09272A31A24A93646614E8B4A8D2913D6DCF5A1586B2D938B6D2BFA9407744C6600712A99C75CA15079B63B89855CC20ED305</span></span>
<span id="cb12-27"><a href="#cb12-27" aria-hidden="true" tabindex="-1"></a><span class="st">F1F4D92C071738C54F2A5379C1D8602BA3D9CA6CA798B0AE0B8E61E08E16621DC93F70FBBFEA4919BDEFF03580A9BC54768DB81BA24BFA66FB97BAB</span></span>
<span id="cb12-28"><a href="#cb12-28" aria-hidden="true" tabindex="-1"></a><span class="st">3BF694739AB7BAF25400EA9E6878C5FBC26ECE97400C8E5A947BC936F1CA009B79AB2686D663DE16E76CD15436E75E4EEF8644C23B5D203095C318B</span></span>
<span id="cb12-29"><a href="#cb12-29" aria-hidden="true" tabindex="-1"></a><span class="st">BC7CD4F0CE581382CB3D84678C5DB8AE75B778EE1858845B97C57F3809DE570C2EC356FF28D4AE6A6E7193A9AA370703088F12998FB1ACBD71AA237</span></span>
<span id="cb12-30"><a href="#cb12-30" aria-hidden="true" tabindex="-1"></a><span class="st">A8E051ECE15C7891D00E29A3CE390344B4A153C21A179CA086EE0B0194A1D3661C0481DFC41C3AE59371A4B4DD598D2BF4FEF835FD1079374673377</span></span>
<span id="cb12-31"><a href="#cb12-31" aria-hidden="true" tabindex="-1"></a><span class="st">5E74C23C851BE1795429148A416F9D1807B6B96830F8EF0B47E982894D51CF2B4DE933A43FA7BB9A52C92F9CE5405E704C58E100F102D26A95558F1</span></span>
<span id="cb12-32"><a href="#cb12-32" aria-hidden="true" tabindex="-1"></a><span class="st">724B7F39E5AE1B405FC13289949F7E6B84BC22B132FB60925A3402C1544E17C8E110ECF19BD4573C05811CAE10A8AD482268B861B59C82B9F156357</span></span>
<span id="cb12-33"><a href="#cb12-33" aria-hidden="true" tabindex="-1"></a><span class="st">891A9CA5B0A8D59003220A2D3C9EC1D22A118AB7EEB407433427F95D737E6CE34B2C20E154&#39;, proof_one_pad=&#39;4F6FE8E88B79B0C82F48247305C</span></span>
<span id="cb12-34"><a href="#cb12-34" aria-hidden="true" tabindex="-1"></a><span class="st">4302ACD9AA8FA5050A517D7ED473FFDF8C129E50AF31B0482C586A16B4DF4E7DDDC0677BBB1C78F1F0E4B9256630CAC74144CCA5954D621D1CC347EF</span></span>
<span id="cb12-35"><a href="#cb12-35" aria-hidden="true" tabindex="-1"></a><span class="st">ED3D13A39D3556FBE9023C17EAD2E77CDC96F7BD9C8C0FCEC8D934F02702B01C85DEE0C67B1F4C167F4D00931F4B7B918268CCCE4351261333F2C90E</span></span>
<span id="cb12-36"><a href="#cb12-36" aria-hidden="true" tabindex="-1"></a><span class="st">1D9FC89BA86086CE1B0D828F4A9B9F2982F7A538ACAA74390237EFDDA83F2ECC3B960606050663E087AB9F2FE51139AE9831FC8418DA4D74AFFF20A4</span></span>
<span id="cb12-37"><a href="#cb12-37" aria-hidden="true" tabindex="-1"></a><span class="st">E09F336B3A17FE33F21CD7403EC1B049FBEBF3EBE00A17386E2082675A3AA74E9E171779D027D7F658F2C46858C8A586A3787F4BF6DEDB185DCC8BF0</span></span>
<span id="cb12-38"><a href="#cb12-38" aria-hidden="true" tabindex="-1"></a><span class="st">C547B695D7E5C3DEC654074F19F79100FB67B3D82E360B5E69F00C6CC1D26520F699929B509DEB690CA639912718574886D845811196E89BB589F4C4</span></span>
<span id="cb12-39"><a href="#cb12-39" aria-hidden="true" tabindex="-1"></a><span class="st">F755943302D704ED4EB31A84E5A78F0D4D9C1AB3AA0E3C58DF0C59CE140E4047EC5303C300443F0A94C3391B6DAE4DC5C01BFCF9F8D7F146BD767CAF</span></span>
<span id="cb12-40"><a href="#cb12-40" aria-hidden="true" tabindex="-1"></a><span class="st">5AE3365D365A385023BAACD23294B4D41B86ADFDF61319808BD809027ACE7399E555E4D32505433C3E14F87B7CBFEC49FA3B12FEE90C924B303C306F</span></span>
<span id="cb12-41"><a href="#cb12-41" aria-hidden="true" tabindex="-1"></a><span class="st">7BE9EA1F3EFB47AFB56B5583CAA9938609C5636184A5E8FE8E6586E0A34E1112957296DE96874488DAF85CE1A2B21CA81765D5DDF0862AFEFEB6A179</span></span>
<span id="cb12-42"><a href="#cb12-42" aria-hidden="true" tabindex="-1"></a><span class="st">E66DEC9F110B3F0B7C819CF5841D91F0997E2&#39;, proof_one_data=&#39;9D47AA0A0C4F7E948594C8C0898B18CE46922F1DB97D15C0E7001DDF571DCB60</span></span>
<span id="cb12-43"><a href="#cb12-43" aria-hidden="true" tabindex="-1"></a><span class="st">3FA20CF667A1D422A0B69C3978DA1FCE209F27F6C5BE9A3D81A6E241FF40C068DB43B8CEB933C95F5CB71CEB98AA1841B8A10215B043861E4785F04E</span></span>
<span id="cb12-44"><a href="#cb12-44" aria-hidden="true" tabindex="-1"></a><span class="st">FD54FB87C20541C3C4066735E97104FD1AC4C6101258F28FF6CE1DC0B4335AA5A1CA059C559584E867C4EE38A83919196E655734169E153F069B1993</span></span>
<span id="cb12-45"><a href="#cb12-45" aria-hidden="true" tabindex="-1"></a><span class="st">013862C95DAFBEEA73BFFFC5640692C2AB744F7036D54E71D9DFC4457C8FF000EBE60213BA0035B7258E9AB812822AB47AB1A5B84CBB4CD4C51C615E</span></span>
<span id="cb12-46"><a href="#cb12-46" aria-hidden="true" tabindex="-1"></a><span class="st">D0FB07C6310D98465EB031C8C3FFDAD52BB994C86C9773D4927AE9AA89B124764047B8EBC8F0FB1019D292E242F40881EBAF61E2937EA2536DF4F3B8</span></span>
<span id="cb12-47"><a href="#cb12-47" aria-hidden="true" tabindex="-1"></a><span class="st">9CE3B42F99AFD53DEF6086358BD080639997A4498BE84345A81A1C24B6E822F30187024FF1FFA60A50D48404A6BD96C0E2CFD82E7182602BBA01A5D8</span></span>
<span id="cb12-48"><a href="#cb12-48" aria-hidden="true" tabindex="-1"></a><span class="st">A188B5A829964396C70D4316050C8A694DE17C7CC32B66AE88AE276CE37C05ECF54E0319627F96F572127556319E3395D0D539DC06AF17F8E11225CA</span></span>
<span id="cb12-49"><a href="#cb12-49" aria-hidden="true" tabindex="-1"></a><span class="st">6807F5CA0E23C14C7CB85B2E58D9B65401EA2F3FC4DA19CD00ACC36F66880A097A282D9F9CF7A88F1AFE7E82AE370ACFDDAB7A368ABB0898EB0544F1</span></span>
<span id="cb12-50"><a href="#cb12-50" aria-hidden="true" tabindex="-1"></a><span class="st">392D6E42D2BD904A4931FD6BACB9FD1CC7A6DD6440729042F153C4A962FA3B54BF214F917CB77A6433E820675DBE4B2C6535D70C6F9932CAA293543D&#39;,</span></span>
<span id="cb12-51"><a href="#cb12-51" aria-hidden="true" tabindex="-1"></a><span class="st">proof_zero_challenge=&#39;A29D945FF0E85DFB371DD51FE7ED6962D1C5AACF7F571B1557B69FCF9EE5999E&#39;, proof_one_challenge=&#39;44DD7A11F2</span></span>
<span id="cb12-52"><a href="#cb12-52" aria-hidden="true" tabindex="-1"></a><span class="st">26AC02D72AC6A7DA7ABE8AF00111832AB512F95DF72F1E72782600&#39;, challenge=&#39;E77B0E71E30F09FE0E489BC7C26827EDC1C6BC52AA0C2E0EB5AD</span></span>
<span id="cb12-53"><a href="#cb12-53" aria-hidden="true" tabindex="-1"></a><span class="st">CEEE115DBF9E&#39;, proof_zero_response=&#39;6A4B3ECBBB601F0DB1D49EA22D1888667706D0BD3C19D850CE48511EBC93EAA1&#39;, proof_one_respons</span></span>
<span id="cb12-54"><a href="#cb12-54" aria-hidden="true" tabindex="-1"></a><span class="st">e=&#39;BA796CFF96207D51E84852655ED4719023799ED479F5F86271F6AC7E9696C87D&#39;, usage=&lt;ProofUsage.SelectionValue: </span><span class="ch">\&quot;</span><span class="st">Prove selectio</span></span>
<span id="cb12-55"><a href="#cb12-55" aria-hidden="true" tabindex="-1"></a><span class="st">n&#39;s value (0 or 1)</span><span class="ch">\&quot;</span><span class="st">&gt;)}</span><span class="ch">\n</span><span class="st">ballot_validator.py.ballot_is_valid_for_election:#L30: ballot_is_valid_for_election: mismatchin</span></span>
<span id="cb12-56"><a href="#cb12-56" aria-hidden="true" tabindex="-1"></a><span class="st">g ballot encryption ballot-ba77bc54-df81-11f0-a9fc-32689d655a73&quot;</span></span>
<span id="cb12-57"><a href="#cb12-57" aria-hidden="true" tabindex="-1"></a>    <span class="fu">},</span></span></code></pre></div>
<h1 id="whats-not-tested">What’s <em>not</em> tested?</h1>
<p>These test can catch some things, but as I said above, they’re not comprehensive.
One important thing that they can’t test for by design is whether the encryption devices are honestly encrypting the right votes or changing them.</p>
<p>For example, say I’m trying to vote for Alice. The encryption device says “OK, here’s your encrypted vote for Alice.” But it actually broadcasts a (properly) encrypted vote for Bob.</p>
<p><img src="baddevice.png" style="max-width: 600px"></img></p>
<p>There’s no way to test for that without keeping a list of who each voter voted for, and that would violate ballot secrecy.
Instead, ElectionGuard has separate mechanisms to catch it:</p>
<ul>
<li>the <a href="/posts/2024/10/15/mechanics-of-the-benaloh-challenge">Benaloh challenge</a> (the “cast or spoil” thing)</li>
<li>risk-limiting audits</li>
</ul>
<p>That’s all for today. Happy Holidays!
The next dev update will be about publishing election artifacts via IPFS.</p>]]></summary>
</entry>
<entry>
    <title>ElectionGuard + Cardano Dev Update #2: Election Verifier</title>
    <link href="https://cryptoisland.blog/posts/2025/12/23/egc-dev02-election-verifier" />
    <id>https://cryptoisland.blog/posts/2025/12/23/egc-dev02-election-verifier</id>
    <published>2025-12-23T00:00:00Z</published>
    <updated>2025-12-23T00:00:00Z</updated>
    <summary type="html"><![CDATA[
<div class="toc"><div class="header">Contents</div>
<ul>
<li><a href="#what-i-did" id="toc-what-i-did">What I did</a></li>
<li><a href="#official-election-artifacts" id="toc-official-election-artifacts">Official election artifacts</a></li>
<li><a href="#dont-trust-verify" id="toc-dont-trust-verify">Don’t trust, verify!</a>
<ul>
<li><a href="#human-readable-log" id="toc-human-readable-log">Human-readable Log</a></li>
<li><a href="#parsable-json-summary" id="toc-parsable-json-summary">Parsable JSON Summary</a></li>
</ul></li>
<li><a href="#design" id="toc-design">Design</a>
<ul>
<li><a href="#dag" id="toc-dag">DAG</a></li>
<li><a href="#verify_-functions" id="toc-verify_-functions"><code>verify_</code> functions</a></li>
</ul></li>
<li><a href="#can-we-break-it" id="toc-can-we-break-it">Can we break it?</a></li>
</ul>
<center class="reminder"><img src="deps-small.png"></img></center></div>
<p>This is dev update #2 for <a href="https://milestones.projectcatalyst.io/projects/1300090">my fund13 project</a>.
You can find the code <a href="https://github.com/jefdaj/electionguard-cardano/tree/trunk/milestone1/verifier">here</a>, and a companion YouTube video <a href="https://www.youtube.com/watch?v=MtkSXy5WDUQ">here</a>.</p>
<p>In <a href="/posts/2025/12/23/egc-dev01-election-demo">the last update</a> I packaged the <a href="https://github.com/jefdaj/electionguard-python">ElectionGuard Python reference implementation</a> into a Docker container and ran an election using multiple instances of it communicating via a shared folder. Today I want to show how to verify the artifacts in that folder.</p>
<h1 id="what-i-did">What I did</h1>
<p>I’m not a cryptographer, so I didn’t attempt to verify or rewrite any of the low level crypto code in the reference implementation here. This was more about rearranging it to be:</p>
<ul>
<li>a little more comprehensive</li>
<li>clearer to read</li>
<li>compatible with my data file format</li>
<li>less monolithic and easier to run incrementally during an election</li>
</ul>
<h1 id="official-election-artifacts">Official election artifacts</h1>
<p>Files generated during a typical election look like this.
You can create them by running the code in <a href="/posts/2025/12/23/egc-dev01-election-demo">the first dev update</a>.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode txt"><code class="sourceCode default"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a>data/</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>├── private</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>│   ├── admin_1</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>│   ├── device_1</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>│   │   └── plaintext_ballots</span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>│   │       ├── ballot-f2f71be0-e023-11f0-8779-ce89c2e804da.json</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>│   │       ├── ballot-f512d9a0-e023-11f0-8c36-ce89c2e804da.json</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>│   │       └── ballot-f71c771a-e023-11f0-8c68-ce89c2e804da.json</span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>│   ├── device_2</span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a>│   │   └── plaintext_ballots</span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>│   │       ├── ballot-f38097b2-e023-11f0-bd15-faf5e3d58ded.json</span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a>│   │       ├── ballot-f595f4d4-e023-11f0-aea1-faf5e3d58ded.json</span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a>│   │       └── ballot-f79e812e-e023-11f0-920c-faf5e3d58ded.json</span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a>│   ├── device_3</span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a>│   │   └── plaintext_ballots</span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a>│   │       ├── ballot-f408e6e4-e023-11f0-ba1e-2eb40f2ca782.json</span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a>│   │       ├── ballot-f616747e-e023-11f0-b569-2eb40f2ca782.json</span>
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true" tabindex="-1"></a>│   │       └── ballot-f82655ae-e023-11f0-bb16-2eb40f2ca782.json</span>
<span id="cb1-19"><a href="#cb1-19" aria-hidden="true" tabindex="-1"></a>│   ├── device_4</span>
<span id="cb1-20"><a href="#cb1-20" aria-hidden="true" tabindex="-1"></a>│   │   └── plaintext_ballots</span>
<span id="cb1-21"><a href="#cb1-21" aria-hidden="true" tabindex="-1"></a>│   │       ├── ballot-f48c8210-e023-11f0-9bda-66b350748407.json</span>
<span id="cb1-22"><a href="#cb1-22" aria-hidden="true" tabindex="-1"></a>│   │       ├── ballot-f69b75b6-e023-11f0-a295-66b350748407.json</span>
<span id="cb1-23"><a href="#cb1-23" aria-hidden="true" tabindex="-1"></a>│   │       └── ballot-f8a750f0-e023-11f0-b410-66b350748407.json</span>
<span id="cb1-24"><a href="#cb1-24" aria-hidden="true" tabindex="-1"></a>│   ├── guardian_1</span>
<span id="cb1-25"><a href="#cb1-25" aria-hidden="true" tabindex="-1"></a>│   │   └── election_key_pair.json</span>
<span id="cb1-26"><a href="#cb1-26" aria-hidden="true" tabindex="-1"></a>│   ├── guardian_2</span>
<span id="cb1-27"><a href="#cb1-27" aria-hidden="true" tabindex="-1"></a>│   │   └── election_key_pair.json</span>
<span id="cb1-28"><a href="#cb1-28" aria-hidden="true" tabindex="-1"></a>│   └── guardian_3</span>
<span id="cb1-29"><a href="#cb1-29" aria-hidden="true" tabindex="-1"></a>│       └── election_key_pair.json</span>
<span id="cb1-30"><a href="#cb1-30" aria-hidden="true" tabindex="-1"></a>└── public</span>
<span id="cb1-31"><a href="#cb1-31" aria-hidden="true" tabindex="-1"></a>    ├── 1_config</span>
<span id="cb1-32"><a href="#cb1-32" aria-hidden="true" tabindex="-1"></a>    │   ├── 1_announce</span>
<span id="cb1-33"><a href="#cb1-33" aria-hidden="true" tabindex="-1"></a>    │   │   ├── 1_manifest.json</span>
<span id="cb1-34"><a href="#cb1-34" aria-hidden="true" tabindex="-1"></a>    │   │   └── 2_ceremony.json</span>
<span id="cb1-35"><a href="#cb1-35" aria-hidden="true" tabindex="-1"></a>    │   ├── 2_ceremony</span>
<span id="cb1-36"><a href="#cb1-36" aria-hidden="true" tabindex="-1"></a>    │   │   ├── 1_pubkeys</span>
<span id="cb1-37"><a href="#cb1-37" aria-hidden="true" tabindex="-1"></a>    │   │   │   ├── guardian_1.json</span>
<span id="cb1-38"><a href="#cb1-38" aria-hidden="true" tabindex="-1"></a>    │   │   │   ├── guardian_2.json</span>
<span id="cb1-39"><a href="#cb1-39" aria-hidden="true" tabindex="-1"></a>    │   │   │   └── guardian_3.json</span>
<span id="cb1-40"><a href="#cb1-40" aria-hidden="true" tabindex="-1"></a>    │   │   ├── 2_backups</span>
<span id="cb1-41"><a href="#cb1-41" aria-hidden="true" tabindex="-1"></a>    │   │   │   ├── guardian_1_backup_2.json</span>
<span id="cb1-42"><a href="#cb1-42" aria-hidden="true" tabindex="-1"></a>    │   │   │   ├── guardian_1_backup_3.json</span>
<span id="cb1-43"><a href="#cb1-43" aria-hidden="true" tabindex="-1"></a>    │   │   │   ├── guardian_2_backup_1.json</span>
<span id="cb1-44"><a href="#cb1-44" aria-hidden="true" tabindex="-1"></a>    │   │   │   ├── guardian_2_backup_3.json</span>
<span id="cb1-45"><a href="#cb1-45" aria-hidden="true" tabindex="-1"></a>    │   │   │   ├── guardian_3_backup_1.json</span>
<span id="cb1-46"><a href="#cb1-46" aria-hidden="true" tabindex="-1"></a>    │   │   │   └── guardian_3_backup_2.json</span>
<span id="cb1-47"><a href="#cb1-47" aria-hidden="true" tabindex="-1"></a>    │   │   └── 3_verifications</span>
<span id="cb1-48"><a href="#cb1-48" aria-hidden="true" tabindex="-1"></a>    │   │       ├── guardian_1_backup_2.json</span>
<span id="cb1-49"><a href="#cb1-49" aria-hidden="true" tabindex="-1"></a>    │   │       ├── guardian_1_backup_3.json</span>
<span id="cb1-50"><a href="#cb1-50" aria-hidden="true" tabindex="-1"></a>    │   │       ├── guardian_2_backup_1.json</span>
<span id="cb1-51"><a href="#cb1-51" aria-hidden="true" tabindex="-1"></a>    │   │       ├── guardian_2_backup_3.json</span>
<span id="cb1-52"><a href="#cb1-52" aria-hidden="true" tabindex="-1"></a>    │   │       ├── guardian_3_backup_1.json</span>
<span id="cb1-53"><a href="#cb1-53" aria-hidden="true" tabindex="-1"></a>    │   │       └── guardian_3_backup_2.json</span>
<span id="cb1-54"><a href="#cb1-54" aria-hidden="true" tabindex="-1"></a>    │   ├── 3_election</span>
<span id="cb1-55"><a href="#cb1-55" aria-hidden="true" tabindex="-1"></a>    │   │   ├── constants.json</span>
<span id="cb1-56"><a href="#cb1-56" aria-hidden="true" tabindex="-1"></a>    │   │   ├── context.json</span>
<span id="cb1-57"><a href="#cb1-57" aria-hidden="true" tabindex="-1"></a>    │   │   └── joint_key.json</span>
<span id="cb1-58"><a href="#cb1-58" aria-hidden="true" tabindex="-1"></a>    │   └── 4_devices</span>
<span id="cb1-59"><a href="#cb1-59" aria-hidden="true" tabindex="-1"></a>    │       ├── device_1.json</span>
<span id="cb1-60"><a href="#cb1-60" aria-hidden="true" tabindex="-1"></a>    │       ├── device_2.json</span>
<span id="cb1-61"><a href="#cb1-61" aria-hidden="true" tabindex="-1"></a>    │       ├── device_3.json</span>
<span id="cb1-62"><a href="#cb1-62" aria-hidden="true" tabindex="-1"></a>    │       └── device_4.json</span>
<span id="cb1-63"><a href="#cb1-63" aria-hidden="true" tabindex="-1"></a>    ├── 2_ballots</span>
<span id="cb1-64"><a href="#cb1-64" aria-hidden="true" tabindex="-1"></a>    │   ├── 1_submitted</span>
<span id="cb1-65"><a href="#cb1-65" aria-hidden="true" tabindex="-1"></a>    │   │   ├── ballot-f2f71be0-e023-11f0-8779-ce89c2e804da.json</span>
<span id="cb1-66"><a href="#cb1-66" aria-hidden="true" tabindex="-1"></a>    │   │   ├── ballot-f38097b2-e023-11f0-bd15-faf5e3d58ded.json</span>
<span id="cb1-67"><a href="#cb1-67" aria-hidden="true" tabindex="-1"></a>    │   │   ├── ballot-f408e6e4-e023-11f0-ba1e-2eb40f2ca782.json</span>
<span id="cb1-68"><a href="#cb1-68" aria-hidden="true" tabindex="-1"></a>    │   │   ├── ballot-f48c8210-e023-11f0-9bda-66b350748407.json</span>
<span id="cb1-69"><a href="#cb1-69" aria-hidden="true" tabindex="-1"></a>    │   │   ├── ballot-f512d9a0-e023-11f0-8c36-ce89c2e804da.json</span>
<span id="cb1-70"><a href="#cb1-70" aria-hidden="true" tabindex="-1"></a>    │   │   ├── ballot-f595f4d4-e023-11f0-aea1-faf5e3d58ded.json</span>
<span id="cb1-71"><a href="#cb1-71" aria-hidden="true" tabindex="-1"></a>    │   │   ├── ballot-f616747e-e023-11f0-b569-2eb40f2ca782.json</span>
<span id="cb1-72"><a href="#cb1-72" aria-hidden="true" tabindex="-1"></a>    │   │   ├── ballot-f69b75b6-e023-11f0-a295-66b350748407.json</span>
<span id="cb1-73"><a href="#cb1-73" aria-hidden="true" tabindex="-1"></a>    │   │   ├── ballot-f71c771a-e023-11f0-8c68-ce89c2e804da.json</span>
<span id="cb1-74"><a href="#cb1-74" aria-hidden="true" tabindex="-1"></a>    │   │   ├── ballot-f79e812e-e023-11f0-920c-faf5e3d58ded.json</span>
<span id="cb1-75"><a href="#cb1-75" aria-hidden="true" tabindex="-1"></a>    │   │   ├── ballot-f82655ae-e023-11f0-bb16-2eb40f2ca782.json</span>
<span id="cb1-76"><a href="#cb1-76" aria-hidden="true" tabindex="-1"></a>    │   │   └── ballot-f8a750f0-e023-11f0-b410-66b350748407.json</span>
<span id="cb1-77"><a href="#cb1-77" aria-hidden="true" tabindex="-1"></a>    │   ├── 2_cast</span>
<span id="cb1-78"><a href="#cb1-78" aria-hidden="true" tabindex="-1"></a>    │   │   ├── ballot-f48c8210-e023-11f0-9bda-66b350748407.json</span>
<span id="cb1-79"><a href="#cb1-79" aria-hidden="true" tabindex="-1"></a>    │   │   ├── ballot-f616747e-e023-11f0-b569-2eb40f2ca782.json</span>
<span id="cb1-80"><a href="#cb1-80" aria-hidden="true" tabindex="-1"></a>    │   │   ├── ballot-f69b75b6-e023-11f0-a295-66b350748407.json</span>
<span id="cb1-81"><a href="#cb1-81" aria-hidden="true" tabindex="-1"></a>    │   │   ├── ballot-f79e812e-e023-11f0-920c-faf5e3d58ded.json</span>
<span id="cb1-82"><a href="#cb1-82" aria-hidden="true" tabindex="-1"></a>    │   │   ├── ballot-f82655ae-e023-11f0-bb16-2eb40f2ca782.json</span>
<span id="cb1-83"><a href="#cb1-83" aria-hidden="true" tabindex="-1"></a>    │   │   └── ballot-f8a750f0-e023-11f0-b410-66b350748407.json</span>
<span id="cb1-84"><a href="#cb1-84" aria-hidden="true" tabindex="-1"></a>    │   └── 3_spoiled</span>
<span id="cb1-85"><a href="#cb1-85" aria-hidden="true" tabindex="-1"></a>    │       ├── ballot-f2f71be0-e023-11f0-8779-ce89c2e804da.json</span>
<span id="cb1-86"><a href="#cb1-86" aria-hidden="true" tabindex="-1"></a>    │       ├── ballot-f38097b2-e023-11f0-bd15-faf5e3d58ded.json</span>
<span id="cb1-87"><a href="#cb1-87" aria-hidden="true" tabindex="-1"></a>    │       ├── ballot-f408e6e4-e023-11f0-ba1e-2eb40f2ca782.json</span>
<span id="cb1-88"><a href="#cb1-88" aria-hidden="true" tabindex="-1"></a>    │       ├── ballot-f512d9a0-e023-11f0-8c36-ce89c2e804da.json</span>
<span id="cb1-89"><a href="#cb1-89" aria-hidden="true" tabindex="-1"></a>    │       ├── ballot-f595f4d4-e023-11f0-aea1-faf5e3d58ded.json</span>
<span id="cb1-90"><a href="#cb1-90" aria-hidden="true" tabindex="-1"></a>    │       └── ballot-f71c771a-e023-11f0-8c68-ce89c2e804da.json</span>
<span id="cb1-91"><a href="#cb1-91" aria-hidden="true" tabindex="-1"></a>    └── 3_results</span>
<span id="cb1-92"><a href="#cb1-92" aria-hidden="true" tabindex="-1"></a>        ├── 1_tally.json</span>
<span id="cb1-93"><a href="#cb1-93" aria-hidden="true" tabindex="-1"></a>        ├── 2_decrypt</span>
<span id="cb1-94"><a href="#cb1-94" aria-hidden="true" tabindex="-1"></a>        │   ├── 1_shares</span>
<span id="cb1-95"><a href="#cb1-95" aria-hidden="true" tabindex="-1"></a>        │   │   ├── 1_tally</span>
<span id="cb1-96"><a href="#cb1-96" aria-hidden="true" tabindex="-1"></a>        │   │   │   ├── tally_guardian_1.json</span>
<span id="cb1-97"><a href="#cb1-97" aria-hidden="true" tabindex="-1"></a>        │   │   │   ├── tally_guardian_2.json</span>
<span id="cb1-98"><a href="#cb1-98" aria-hidden="true" tabindex="-1"></a>        │   │   │   └── tally_guardian_3.json</span>
<span id="cb1-99"><a href="#cb1-99" aria-hidden="true" tabindex="-1"></a>        │   │   └── 2_spoiled</span>
<span id="cb1-100"><a href="#cb1-100" aria-hidden="true" tabindex="-1"></a>        │   │       ├── ballot-f2f71be0-e023-11f0-8779-ce89c2e804da_guardian_1.json</span>
<span id="cb1-101"><a href="#cb1-101" aria-hidden="true" tabindex="-1"></a>        │   │       ├── ballot-f2f71be0-e023-11f0-8779-ce89c2e804da_guardian_2.json</span>
<span id="cb1-102"><a href="#cb1-102" aria-hidden="true" tabindex="-1"></a>        │   │       ├── ballot-f2f71be0-e023-11f0-8779-ce89c2e804da_guardian_3.json</span>
<span id="cb1-103"><a href="#cb1-103" aria-hidden="true" tabindex="-1"></a>        │   │       ├── ballot-f38097b2-e023-11f0-bd15-faf5e3d58ded_guardian_1.json</span>
<span id="cb1-104"><a href="#cb1-104" aria-hidden="true" tabindex="-1"></a>        │   │       ├── ballot-f38097b2-e023-11f0-bd15-faf5e3d58ded_guardian_2.json</span>
<span id="cb1-105"><a href="#cb1-105" aria-hidden="true" tabindex="-1"></a>        │   │       ├── ballot-f38097b2-e023-11f0-bd15-faf5e3d58ded_guardian_3.json</span>
<span id="cb1-106"><a href="#cb1-106" aria-hidden="true" tabindex="-1"></a>        │   │       ├── ballot-f408e6e4-e023-11f0-ba1e-2eb40f2ca782_guardian_1.json</span>
<span id="cb1-107"><a href="#cb1-107" aria-hidden="true" tabindex="-1"></a>        │   │       ├── ballot-f408e6e4-e023-11f0-ba1e-2eb40f2ca782_guardian_2.json</span>
<span id="cb1-108"><a href="#cb1-108" aria-hidden="true" tabindex="-1"></a>        │   │       ├── ballot-f408e6e4-e023-11f0-ba1e-2eb40f2ca782_guardian_3.json</span>
<span id="cb1-109"><a href="#cb1-109" aria-hidden="true" tabindex="-1"></a>        │   │       ├── ballot-f512d9a0-e023-11f0-8c36-ce89c2e804da_guardian_1.json</span>
<span id="cb1-110"><a href="#cb1-110" aria-hidden="true" tabindex="-1"></a>        │   │       ├── ballot-f512d9a0-e023-11f0-8c36-ce89c2e804da_guardian_2.json</span>
<span id="cb1-111"><a href="#cb1-111" aria-hidden="true" tabindex="-1"></a>        │   │       ├── ballot-f512d9a0-e023-11f0-8c36-ce89c2e804da_guardian_3.json</span>
<span id="cb1-112"><a href="#cb1-112" aria-hidden="true" tabindex="-1"></a>        │   │       ├── ballot-f595f4d4-e023-11f0-aea1-faf5e3d58ded_guardian_1.json</span>
<span id="cb1-113"><a href="#cb1-113" aria-hidden="true" tabindex="-1"></a>        │   │       ├── ballot-f595f4d4-e023-11f0-aea1-faf5e3d58ded_guardian_2.json</span>
<span id="cb1-114"><a href="#cb1-114" aria-hidden="true" tabindex="-1"></a>        │   │       ├── ballot-f595f4d4-e023-11f0-aea1-faf5e3d58ded_guardian_3.json</span>
<span id="cb1-115"><a href="#cb1-115" aria-hidden="true" tabindex="-1"></a>        │   │       ├── ballot-f71c771a-e023-11f0-8c68-ce89c2e804da_guardian_1.json</span>
<span id="cb1-116"><a href="#cb1-116" aria-hidden="true" tabindex="-1"></a>        │   │       ├── ballot-f71c771a-e023-11f0-8c68-ce89c2e804da_guardian_2.json</span>
<span id="cb1-117"><a href="#cb1-117" aria-hidden="true" tabindex="-1"></a>        │   │       └── ballot-f71c771a-e023-11f0-8c68-ce89c2e804da_guardian_3.json</span>
<span id="cb1-118"><a href="#cb1-118" aria-hidden="true" tabindex="-1"></a>        │   └── 2_combined</span>
<span id="cb1-119"><a href="#cb1-119" aria-hidden="true" tabindex="-1"></a>        │       ├── 1_tally.json</span>
<span id="cb1-120"><a href="#cb1-120" aria-hidden="true" tabindex="-1"></a>        │       └── 2_spoiled</span>
<span id="cb1-121"><a href="#cb1-121" aria-hidden="true" tabindex="-1"></a>        │           ├── ballot-f2f71be0-e023-11f0-8779-ce89c2e804da.json</span>
<span id="cb1-122"><a href="#cb1-122" aria-hidden="true" tabindex="-1"></a>        │           ├── ballot-f38097b2-e023-11f0-bd15-faf5e3d58ded.json</span>
<span id="cb1-123"><a href="#cb1-123" aria-hidden="true" tabindex="-1"></a>        │           ├── ballot-f408e6e4-e023-11f0-ba1e-2eb40f2ca782.json</span>
<span id="cb1-124"><a href="#cb1-124" aria-hidden="true" tabindex="-1"></a>        │           ├── ballot-f512d9a0-e023-11f0-8c36-ce89c2e804da.json</span>
<span id="cb1-125"><a href="#cb1-125" aria-hidden="true" tabindex="-1"></a>        │           ├── ballot-f595f4d4-e023-11f0-aea1-faf5e3d58ded.json</span>
<span id="cb1-126"><a href="#cb1-126" aria-hidden="true" tabindex="-1"></a>        │           └── ballot-f71c771a-e023-11f0-8c68-ce89c2e804da.json</span>
<span id="cb1-127"><a href="#cb1-127" aria-hidden="true" tabindex="-1"></a>        └── 3_summary.json</span>
<span id="cb1-128"><a href="#cb1-128" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-129"><a href="#cb1-129" aria-hidden="true" tabindex="-1"></a>34 directories, 93 files</span></code></pre></div>
<p>In milestone 2 I plan to post everything under <code>data/public</code> to Cardano + IPFS.</p>
<p>Later, in a real election, I hope these files will be published by election officials in close to real time (allowing for optional delays to post batches of ballots for improved voter privacy). There will be an indexer app (developed in milestone 2 + 3) to allow any interested observer to keep their local copy in sync as an election progresses.</p>
<p><em>Side note: the plaintext ballots should <em>not</em> be kept by actual ElectionGuard voting machines.
In a future hypothetical version of the protocol with staking, I might even favor having them post a signed message saying they’ve securely deleted each cast ballot so that they can be slashed if anyone can produce the plaintext.</em></p>
<p>The artifacts come with a summary of the results:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode json"><code class="sourceCode json"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="fu">{</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;Tally of all cast ballots&quot;</span><span class="fu">:</span> <span class="ot">[</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>    <span class="fu">{</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>      <span class="dt">&quot;question&quot;</span><span class="fu">:</span> <span class="st">&quot;Should pineapple be banned on pizza?&quot;</span><span class="fu">,</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>      <span class="dt">&quot;votes&quot;</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;Unsure&quot;</span><span class="fu">:</span> <span class="dv">3</span><span class="fu">,</span></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;No&quot;</span><span class="fu">:</span> <span class="dv">2</span><span class="fu">,</span></span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;Yes&quot;</span><span class="fu">:</span> <span class="dv">1</span></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a>      <span class="fu">}</span></span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a>    <span class="fu">}</span></span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a>  <span class="ot">]</span><span class="fu">,</span></span>
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;Individual spoiled ballots&quot;</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb2-13"><a href="#cb2-13" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;f512d9a0-e023-11f0-8c36-ce89c2e804da&quot;</span><span class="fu">:</span> <span class="ot">[</span></span>
<span id="cb2-14"><a href="#cb2-14" aria-hidden="true" tabindex="-1"></a>      <span class="fu">{</span></span>
<span id="cb2-15"><a href="#cb2-15" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;Should pineapple be banned on pizza?&quot;</span><span class="fu">:</span> <span class="st">&quot;No&quot;</span></span>
<span id="cb2-16"><a href="#cb2-16" aria-hidden="true" tabindex="-1"></a>      <span class="fu">}</span></span>
<span id="cb2-17"><a href="#cb2-17" aria-hidden="true" tabindex="-1"></a>    <span class="ot">]</span><span class="fu">,</span></span>
<span id="cb2-18"><a href="#cb2-18" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;f38097b2-e023-11f0-bd15-faf5e3d58ded&quot;</span><span class="fu">:</span> <span class="ot">[</span></span>
<span id="cb2-19"><a href="#cb2-19" aria-hidden="true" tabindex="-1"></a>      <span class="fu">{</span></span>
<span id="cb2-20"><a href="#cb2-20" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;Should pineapple be banned on pizza?&quot;</span><span class="fu">:</span> <span class="st">&quot;Yes&quot;</span></span>
<span id="cb2-21"><a href="#cb2-21" aria-hidden="true" tabindex="-1"></a>      <span class="fu">}</span></span>
<span id="cb2-22"><a href="#cb2-22" aria-hidden="true" tabindex="-1"></a>    <span class="ot">]</span><span class="fu">,</span></span>
<span id="cb2-23"><a href="#cb2-23" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;f595f4d4-e023-11f0-aea1-faf5e3d58ded&quot;</span><span class="fu">:</span> <span class="ot">[</span></span>
<span id="cb2-24"><a href="#cb2-24" aria-hidden="true" tabindex="-1"></a>      <span class="fu">{</span></span>
<span id="cb2-25"><a href="#cb2-25" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;Should pineapple be banned on pizza?&quot;</span><span class="fu">:</span> <span class="st">&quot;No&quot;</span></span>
<span id="cb2-26"><a href="#cb2-26" aria-hidden="true" tabindex="-1"></a>      <span class="fu">}</span></span>
<span id="cb2-27"><a href="#cb2-27" aria-hidden="true" tabindex="-1"></a>    <span class="ot">]</span><span class="fu">,</span></span>
<span id="cb2-28"><a href="#cb2-28" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;f2f71be0-e023-11f0-8779-ce89c2e804da&quot;</span><span class="fu">:</span> <span class="ot">[</span></span>
<span id="cb2-29"><a href="#cb2-29" aria-hidden="true" tabindex="-1"></a>      <span class="fu">{</span></span>
<span id="cb2-30"><a href="#cb2-30" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;Should pineapple be banned on pizza?&quot;</span><span class="fu">:</span> <span class="st">&quot;Yes&quot;</span></span>
<span id="cb2-31"><a href="#cb2-31" aria-hidden="true" tabindex="-1"></a>      <span class="fu">}</span></span>
<span id="cb2-32"><a href="#cb2-32" aria-hidden="true" tabindex="-1"></a>    <span class="ot">]</span><span class="fu">,</span></span>
<span id="cb2-33"><a href="#cb2-33" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;f408e6e4-e023-11f0-ba1e-2eb40f2ca782&quot;</span><span class="fu">:</span> <span class="ot">[</span></span>
<span id="cb2-34"><a href="#cb2-34" aria-hidden="true" tabindex="-1"></a>      <span class="fu">{</span></span>
<span id="cb2-35"><a href="#cb2-35" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;Should pineapple be banned on pizza?&quot;</span><span class="fu">:</span> <span class="st">&quot;Yes&quot;</span></span>
<span id="cb2-36"><a href="#cb2-36" aria-hidden="true" tabindex="-1"></a>      <span class="fu">}</span></span>
<span id="cb2-37"><a href="#cb2-37" aria-hidden="true" tabindex="-1"></a>    <span class="ot">]</span><span class="fu">,</span></span>
<span id="cb2-38"><a href="#cb2-38" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;f71c771a-e023-11f0-8c68-ce89c2e804da&quot;</span><span class="fu">:</span> <span class="ot">[</span></span>
<span id="cb2-39"><a href="#cb2-39" aria-hidden="true" tabindex="-1"></a>      <span class="fu">{</span></span>
<span id="cb2-40"><a href="#cb2-40" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;Should pineapple be banned on pizza?&quot;</span><span class="fu">:</span> <span class="st">&quot;Unsure&quot;</span></span>
<span id="cb2-41"><a href="#cb2-41" aria-hidden="true" tabindex="-1"></a>      <span class="fu">}</span></span>
<span id="cb2-42"><a href="#cb2-42" aria-hidden="true" tabindex="-1"></a>    <span class="ot">]</span></span>
<span id="cb2-43"><a href="#cb2-43" aria-hidden="true" tabindex="-1"></a>  <span class="fu">}</span></span>
<span id="cb2-44"><a href="#cb2-44" aria-hidden="true" tabindex="-1"></a><span class="fu">}</span></span></code></pre></div>
<p>But, can we really trust the election officials to report that honestly?</p>
<h1 id="dont-trust-verify">Don’t trust, verify!</h1>
<p>Any interested observer will be able to run one of hopefully many independently developed and audited verifiers to double check that the ElectionGuard protocol is being followed correctly, and (in later versions) to post certifications or disputes on chain.</p>
<p>Today’s code is a first draft of one possible verifier. Here’s how you can try it.</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> cd electionguard-cardano/milestone1/verifier</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix develop</span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> ./verifier.py</span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a><span class="ex">/nix/store/wy5s1xijg5v4m1y26gk25vzz1xzd5m60-docker-compose.yaml</span></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a> <span class="ex">Container</span> verifier-verifier1-1  Starting</span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a> <span class="ex">Container</span> verifier-verifier1-1  Started</span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a><span class="ex">docker</span> exec verifier-verifier1-1 poetry run /scripts/verifier.py verify <span class="at">--public-dir</span> /data/public <span class="at">--verifier-id</span> verifier1 <span class="at">--logfile</span> /data/public/verify.log</span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a> <span class="ex">Container</span> verifier-verifier1-1  Stopping</span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a> <span class="ex">Container</span> verifier-verifier1-1  Stopped</span>
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a> <span class="ex">Container</span> verifier-verifier1-1  Removing</span>
<span id="cb3-13"><a href="#cb3-13" aria-hidden="true" tabindex="-1"></a> <span class="ex">Container</span> verifier-verifier1-1  Removed</span>
<span id="cb3-14"><a href="#cb3-14" aria-hidden="true" tabindex="-1"></a> <span class="ex">Network</span> verifier  Removing</span>
<span id="cb3-15"><a href="#cb3-15" aria-hidden="true" tabindex="-1"></a> <span class="ex">Network</span> verifier  Removed</span></code></pre></div>
<p>The terminal output isn’t very interesting; the files we want to look at are:</p>
<ul>
<li><code>data/public/verify.log</code> (a temporary location for the human-readable log)</li>
<li><code>data/public/4_verify/verifier1.json</code> (the part that will go on chain)</li>
</ul>
<h2 id="human-readable-log">Human-readable Log</h2>
<div class="sourceCode" id="cb4"><pre class="sourceCode txt"><code class="sourceCode default"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a>Verifying announcement:</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>✅ manifest</span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>✅ ceremony_details</span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>Verifying key ceremony:</span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a>✅ guardian_pubkey {&#39;guardian_id&#39;: &#39;guardian_1&#39;}</span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a>✅ guardian_pubkey {&#39;guardian_id&#39;: &#39;guardian_2&#39;}</span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a>✅ guardian_pubkey {&#39;guardian_id&#39;: &#39;guardian_3&#39;}</span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a>✅ guardian_backup {&#39;guardian_id&#39;: &#39;guardian_1&#39;, &#39;backup_order&#39;: 2}</span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a>✅ guardian_backup {&#39;guardian_id&#39;: &#39;guardian_1&#39;, &#39;backup_order&#39;: 3}</span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a>✅ guardian_backup {&#39;guardian_id&#39;: &#39;guardian_2&#39;, &#39;backup_order&#39;: 1}</span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a>✅ guardian_backup {&#39;guardian_id&#39;: &#39;guardian_2&#39;, &#39;backup_order&#39;: 3}</span>
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true" tabindex="-1"></a>✅ guardian_backup {&#39;guardian_id&#39;: &#39;guardian_3&#39;, &#39;backup_order&#39;: 1}</span>
<span id="cb4-14"><a href="#cb4-14" aria-hidden="true" tabindex="-1"></a>✅ guardian_backup {&#39;guardian_id&#39;: &#39;guardian_3&#39;, &#39;backup_order&#39;: 2}</span>
<span id="cb4-15"><a href="#cb4-15" aria-hidden="true" tabindex="-1"></a>✅ guardian_verification {&#39;guardian_id&#39;: &#39;guardian_1&#39;, &#39;backup_order&#39;: 2}</span>
<span id="cb4-16"><a href="#cb4-16" aria-hidden="true" tabindex="-1"></a>✅ guardian_verification {&#39;guardian_id&#39;: &#39;guardian_1&#39;, &#39;backup_order&#39;: 3}</span>
<span id="cb4-17"><a href="#cb4-17" aria-hidden="true" tabindex="-1"></a>✅ guardian_verification {&#39;guardian_id&#39;: &#39;guardian_2&#39;, &#39;backup_order&#39;: 1}</span>
<span id="cb4-18"><a href="#cb4-18" aria-hidden="true" tabindex="-1"></a>✅ guardian_verification {&#39;guardian_id&#39;: &#39;guardian_2&#39;, &#39;backup_order&#39;: 3}</span>
<span id="cb4-19"><a href="#cb4-19" aria-hidden="true" tabindex="-1"></a>✅ guardian_verification {&#39;guardian_id&#39;: &#39;guardian_3&#39;, &#39;backup_order&#39;: 1}</span>
<span id="cb4-20"><a href="#cb4-20" aria-hidden="true" tabindex="-1"></a>✅ guardian_verification {&#39;guardian_id&#39;: &#39;guardian_3&#39;, &#39;backup_order&#39;: 2}</span>
<span id="cb4-21"><a href="#cb4-21" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-22"><a href="#cb4-22" aria-hidden="true" tabindex="-1"></a>Verifying election constants:</span>
<span id="cb4-23"><a href="#cb4-23" aria-hidden="true" tabindex="-1"></a>✅ joint_key</span>
<span id="cb4-24"><a href="#cb4-24" aria-hidden="true" tabindex="-1"></a>✅ constants</span>
<span id="cb4-25"><a href="#cb4-25" aria-hidden="true" tabindex="-1"></a>✅ internal_manifest</span>
<span id="cb4-26"><a href="#cb4-26" aria-hidden="true" tabindex="-1"></a>✅ context</span>
<span id="cb4-27"><a href="#cb4-27" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-28"><a href="#cb4-28" aria-hidden="true" tabindex="-1"></a>Verifying 4 encryption devices:</span>
<span id="cb4-29"><a href="#cb4-29" aria-hidden="true" tabindex="-1"></a>✅ device {&#39;device_number&#39;: 1}</span>
<span id="cb4-30"><a href="#cb4-30" aria-hidden="true" tabindex="-1"></a>✅ device {&#39;device_number&#39;: 2}</span>
<span id="cb4-31"><a href="#cb4-31" aria-hidden="true" tabindex="-1"></a>✅ device {&#39;device_number&#39;: 3}</span>
<span id="cb4-32"><a href="#cb4-32" aria-hidden="true" tabindex="-1"></a>✅ device {&#39;device_number&#39;: 4}</span>
<span id="cb4-33"><a href="#cb4-33" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-34"><a href="#cb4-34" aria-hidden="true" tabindex="-1"></a>Verifying 12 submitted ballots:</span>
<span id="cb4-35"><a href="#cb4-35" aria-hidden="true" tabindex="-1"></a>✅ ballot_submitted {&#39;ballot_id&#39;: &#39;ballot-f8a750f0-e023-11f0-b410-66b350748407&#39;}</span>
<span id="cb4-36"><a href="#cb4-36" aria-hidden="true" tabindex="-1"></a>✅ ballot_submitted {&#39;ballot_id&#39;: &#39;ballot-f48c8210-e023-11f0-9bda-66b350748407&#39;}</span>
<span id="cb4-37"><a href="#cb4-37" aria-hidden="true" tabindex="-1"></a>✅ ballot_submitted {&#39;ballot_id&#39;: &#39;ballot-f82655ae-e023-11f0-bb16-2eb40f2ca782&#39;}</span>
<span id="cb4-38"><a href="#cb4-38" aria-hidden="true" tabindex="-1"></a>✅ ballot_submitted {&#39;ballot_id&#39;: &#39;ballot-f512d9a0-e023-11f0-8c36-ce89c2e804da&#39;}</span>
<span id="cb4-39"><a href="#cb4-39" aria-hidden="true" tabindex="-1"></a>✅ ballot_submitted {&#39;ballot_id&#39;: &#39;ballot-f79e812e-e023-11f0-920c-faf5e3d58ded&#39;}</span>
<span id="cb4-40"><a href="#cb4-40" aria-hidden="true" tabindex="-1"></a>✅ ballot_submitted {&#39;ballot_id&#39;: &#39;ballot-f38097b2-e023-11f0-bd15-faf5e3d58ded&#39;}</span>
<span id="cb4-41"><a href="#cb4-41" aria-hidden="true" tabindex="-1"></a>✅ ballot_submitted {&#39;ballot_id&#39;: &#39;ballot-f616747e-e023-11f0-b569-2eb40f2ca782&#39;}</span>
<span id="cb4-42"><a href="#cb4-42" aria-hidden="true" tabindex="-1"></a>✅ ballot_submitted {&#39;ballot_id&#39;: &#39;ballot-f595f4d4-e023-11f0-aea1-faf5e3d58ded&#39;}</span>
<span id="cb4-43"><a href="#cb4-43" aria-hidden="true" tabindex="-1"></a>✅ ballot_submitted {&#39;ballot_id&#39;: &#39;ballot-f69b75b6-e023-11f0-a295-66b350748407&#39;}</span>
<span id="cb4-44"><a href="#cb4-44" aria-hidden="true" tabindex="-1"></a>✅ ballot_submitted {&#39;ballot_id&#39;: &#39;ballot-f2f71be0-e023-11f0-8779-ce89c2e804da&#39;}</span>
<span id="cb4-45"><a href="#cb4-45" aria-hidden="true" tabindex="-1"></a>✅ ballot_submitted {&#39;ballot_id&#39;: &#39;ballot-f408e6e4-e023-11f0-ba1e-2eb40f2ca782&#39;}</span>
<span id="cb4-46"><a href="#cb4-46" aria-hidden="true" tabindex="-1"></a>✅ ballot_submitted {&#39;ballot_id&#39;: &#39;ballot-f71c771a-e023-11f0-8c68-ce89c2e804da&#39;}</span>
<span id="cb4-47"><a href="#cb4-47" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-48"><a href="#cb4-48" aria-hidden="true" tabindex="-1"></a>Verifying 6 cast ballots:</span>
<span id="cb4-49"><a href="#cb4-49" aria-hidden="true" tabindex="-1"></a>✅ cast_notice {&#39;ballot_id&#39;: &#39;ballot-f8a750f0-e023-11f0-b410-66b350748407&#39;}</span>
<span id="cb4-50"><a href="#cb4-50" aria-hidden="true" tabindex="-1"></a>✅ cast_notice {&#39;ballot_id&#39;: &#39;ballot-f48c8210-e023-11f0-9bda-66b350748407&#39;}</span>
<span id="cb4-51"><a href="#cb4-51" aria-hidden="true" tabindex="-1"></a>✅ cast_notice {&#39;ballot_id&#39;: &#39;ballot-f82655ae-e023-11f0-bb16-2eb40f2ca782&#39;}</span>
<span id="cb4-52"><a href="#cb4-52" aria-hidden="true" tabindex="-1"></a>✅ cast_notice {&#39;ballot_id&#39;: &#39;ballot-f79e812e-e023-11f0-920c-faf5e3d58ded&#39;}</span>
<span id="cb4-53"><a href="#cb4-53" aria-hidden="true" tabindex="-1"></a>✅ cast_notice {&#39;ballot_id&#39;: &#39;ballot-f616747e-e023-11f0-b569-2eb40f2ca782&#39;}</span>
<span id="cb4-54"><a href="#cb4-54" aria-hidden="true" tabindex="-1"></a>✅ cast_notice {&#39;ballot_id&#39;: &#39;ballot-f69b75b6-e023-11f0-a295-66b350748407&#39;}</span>
<span id="cb4-55"><a href="#cb4-55" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-56"><a href="#cb4-56" aria-hidden="true" tabindex="-1"></a>Verifying 6 spoiled ballots:</span>
<span id="cb4-57"><a href="#cb4-57" aria-hidden="true" tabindex="-1"></a>✅ ballot_spoiled {&#39;ballot_id&#39;: &#39;ballot-f512d9a0-e023-11f0-8c36-ce89c2e804da&#39;}</span>
<span id="cb4-58"><a href="#cb4-58" aria-hidden="true" tabindex="-1"></a>✅ ballot_spoiled {&#39;ballot_id&#39;: &#39;ballot-f38097b2-e023-11f0-bd15-faf5e3d58ded&#39;}</span>
<span id="cb4-59"><a href="#cb4-59" aria-hidden="true" tabindex="-1"></a>✅ ballot_spoiled {&#39;ballot_id&#39;: &#39;ballot-f595f4d4-e023-11f0-aea1-faf5e3d58ded&#39;}</span>
<span id="cb4-60"><a href="#cb4-60" aria-hidden="true" tabindex="-1"></a>✅ ballot_spoiled {&#39;ballot_id&#39;: &#39;ballot-f2f71be0-e023-11f0-8779-ce89c2e804da&#39;}</span>
<span id="cb4-61"><a href="#cb4-61" aria-hidden="true" tabindex="-1"></a>✅ ballot_spoiled {&#39;ballot_id&#39;: &#39;ballot-f408e6e4-e023-11f0-ba1e-2eb40f2ca782&#39;}</span>
<span id="cb4-62"><a href="#cb4-62" aria-hidden="true" tabindex="-1"></a>✅ ballot_spoiled {&#39;ballot_id&#39;: &#39;ballot-f71c771a-e023-11f0-8c68-ce89c2e804da&#39;}</span>
<span id="cb4-63"><a href="#cb4-63" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-64"><a href="#cb4-64" aria-hidden="true" tabindex="-1"></a>Verifying 6 spoiled ballot decyptions:</span>
<span id="cb4-65"><a href="#cb4-65" aria-hidden="true" tabindex="-1"></a>✅ spoiled_result {&#39;ballot_id&#39;: &#39;ballot-f512d9a0-e023-11f0-8c36-ce89c2e804da&#39;}</span>
<span id="cb4-66"><a href="#cb4-66" aria-hidden="true" tabindex="-1"></a>✅ spoiled_result {&#39;ballot_id&#39;: &#39;ballot-f38097b2-e023-11f0-bd15-faf5e3d58ded&#39;}</span>
<span id="cb4-67"><a href="#cb4-67" aria-hidden="true" tabindex="-1"></a>✅ spoiled_result {&#39;ballot_id&#39;: &#39;ballot-f595f4d4-e023-11f0-aea1-faf5e3d58ded&#39;}</span>
<span id="cb4-68"><a href="#cb4-68" aria-hidden="true" tabindex="-1"></a>✅ spoiled_result {&#39;ballot_id&#39;: &#39;ballot-f2f71be0-e023-11f0-8779-ce89c2e804da&#39;}</span>
<span id="cb4-69"><a href="#cb4-69" aria-hidden="true" tabindex="-1"></a>✅ spoiled_result {&#39;ballot_id&#39;: &#39;ballot-f408e6e4-e023-11f0-ba1e-2eb40f2ca782&#39;}</span>
<span id="cb4-70"><a href="#cb4-70" aria-hidden="true" tabindex="-1"></a>✅ spoiled_result {&#39;ballot_id&#39;: &#39;ballot-f71c771a-e023-11f0-8c68-ce89c2e804da&#39;}</span>
<span id="cb4-71"><a href="#cb4-71" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-72"><a href="#cb4-72" aria-hidden="true" tabindex="-1"></a>Verifying ballot ID sets:</span>
<span id="cb4-73"><a href="#cb4-73" aria-hidden="true" tabindex="-1"></a>✅ 6 ballots spoiled = 6 ballots decrypted</span>
<span id="cb4-74"><a href="#cb4-74" aria-hidden="true" tabindex="-1"></a>✅ 6 ballots cast + 6 ballots spoiled = 12 ballots submitted</span>
<span id="cb4-75"><a href="#cb4-75" aria-hidden="true" tabindex="-1"></a>✅ set(spoiled ballot IDs) = set(decrypted ballot IDs)</span>
<span id="cb4-76"><a href="#cb4-76" aria-hidden="true" tabindex="-1"></a>✅ set(cast ballot IDs) + set(spoiled ballot IDs) = set(submitted ballot IDs)</span>
<span id="cb4-77"><a href="#cb4-77" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-78"><a href="#cb4-78" aria-hidden="true" tabindex="-1"></a>Verifying final tally:</span>
<span id="cb4-79"><a href="#cb4-79" aria-hidden="true" tabindex="-1"></a>✅ ciphertext_tally format is valid</span>
<span id="cb4-80"><a href="#cb4-80" aria-hidden="true" tabindex="-1"></a>✅ ciphertext_tally is the correct aggregation of the 6 cast ballots</span>
<span id="cb4-81"><a href="#cb4-81" aria-hidden="true" tabindex="-1"></a>✅ plaintext_tally format is valid</span>
<span id="cb4-82"><a href="#cb4-82" aria-hidden="true" tabindex="-1"></a>✅ plaintext_tally guardian decryption shares are valid</span>
<span id="cb4-83"><a href="#cb4-83" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-84"><a href="#cb4-84" aria-hidden="true" tabindex="-1"></a>Individual spoiled ballots:</span>
<span id="cb4-85"><a href="#cb4-85" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-86"><a href="#cb4-86" aria-hidden="true" tabindex="-1"></a>f512d9a0-e023-11f0-8c36-ce89c2e804da</span>
<span id="cb4-87"><a href="#cb4-87" aria-hidden="true" tabindex="-1"></a>  Should pineapple be banned on pizza? No</span>
<span id="cb4-88"><a href="#cb4-88" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-89"><a href="#cb4-89" aria-hidden="true" tabindex="-1"></a>f38097b2-e023-11f0-bd15-faf5e3d58ded</span>
<span id="cb4-90"><a href="#cb4-90" aria-hidden="true" tabindex="-1"></a>  Should pineapple be banned on pizza? Yes</span>
<span id="cb4-91"><a href="#cb4-91" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-92"><a href="#cb4-92" aria-hidden="true" tabindex="-1"></a>f595f4d4-e023-11f0-aea1-faf5e3d58ded</span>
<span id="cb4-93"><a href="#cb4-93" aria-hidden="true" tabindex="-1"></a>  Should pineapple be banned on pizza? No</span>
<span id="cb4-94"><a href="#cb4-94" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-95"><a href="#cb4-95" aria-hidden="true" tabindex="-1"></a>f2f71be0-e023-11f0-8779-ce89c2e804da</span>
<span id="cb4-96"><a href="#cb4-96" aria-hidden="true" tabindex="-1"></a>  Should pineapple be banned on pizza? Yes</span>
<span id="cb4-97"><a href="#cb4-97" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-98"><a href="#cb4-98" aria-hidden="true" tabindex="-1"></a>f408e6e4-e023-11f0-ba1e-2eb40f2ca782</span>
<span id="cb4-99"><a href="#cb4-99" aria-hidden="true" tabindex="-1"></a>  Should pineapple be banned on pizza? Yes</span>
<span id="cb4-100"><a href="#cb4-100" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-101"><a href="#cb4-101" aria-hidden="true" tabindex="-1"></a>f71c771a-e023-11f0-8c68-ce89c2e804da</span>
<span id="cb4-102"><a href="#cb4-102" aria-hidden="true" tabindex="-1"></a>  Should pineapple be banned on pizza? Unsure</span>
<span id="cb4-103"><a href="#cb4-103" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-104"><a href="#cb4-104" aria-hidden="true" tabindex="-1"></a>Final tally of cast ballots:</span>
<span id="cb4-105"><a href="#cb4-105" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-106"><a href="#cb4-106" aria-hidden="true" tabindex="-1"></a>Should pineapple be banned on pizza?</span>
<span id="cb4-107"><a href="#cb4-107" aria-hidden="true" tabindex="-1"></a>  3 Unsure</span>
<span id="cb4-108"><a href="#cb4-108" aria-hidden="true" tabindex="-1"></a>  2 No</span>
<span id="cb4-109"><a href="#cb4-109" aria-hidden="true" tabindex="-1"></a>  1 Yes</span>
<span id="cb4-110"><a href="#cb4-110" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-111"><a href="#cb4-111" aria-hidden="true" tabindex="-1"></a>🎉 The election has been verified!</span></code></pre></div>
<p>The summary looks the same as the one printed out by the admin container in the original <a href="https://github.com/jefdaj/electionguard-cardano/tree/trunk/milestone1/election/election.py">election.py</a> run,
but now we (an observer) <em>can</em> trust it because we ran all the crypto operations locally ourselves.</p>
<h2 id="parsable-json-summary">Parsable JSON Summary</h2>
<p>This is the version that will be posted on chain.
I also hope independent observers will run dashboards showing live incremental verification progress,
so I’ll evolve this format to make that as easy as possible.</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode json"><code class="sourceCode json"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="fu">{</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;Verified&quot;</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;manifest&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;ceremony_details&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;gather_announce&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;all_guardian_backups&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;all_guardian_verifications&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;gather_ceremony&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;joint_key&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;build_election&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;constants&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;internal_manifest&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;context&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;gather_constants&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-15"><a href="#cb5-15" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;all_devices&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-16"><a href="#cb5-16" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;gather_config&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-17"><a href="#cb5-17" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;all_ballots_submitted&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-18"><a href="#cb5-18" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;all_ballots_cast&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-19"><a href="#cb5-19" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;all_ballots_spoiled&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-20"><a href="#cb5-20" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;all_spoiled_results&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-21"><a href="#cb5-21" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;n_spoiled_decrypted&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-22"><a href="#cb5-22" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;n_cast_spoiled_submitted&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-23"><a href="#cb5-23" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;set_spoiled_decrypted&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-24"><a href="#cb5-24" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;set_cast_spoiled_submitted&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-25"><a href="#cb5-25" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;ballot_sets&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-26"><a href="#cb5-26" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;ciphertext_tally&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-27"><a href="#cb5-27" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;tally_aggregation&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-28"><a href="#cb5-28" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;plaintext_tally&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-29"><a href="#cb5-29" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;tally_decryption&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-30"><a href="#cb5-30" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;gather_tally&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-31"><a href="#cb5-31" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;gather_decryptions&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb5-32"><a href="#cb5-32" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;gather_election&quot;</span><span class="fu">:</span> <span class="kw">true</span></span>
<span id="cb5-33"><a href="#cb5-33" aria-hidden="true" tabindex="-1"></a>  <span class="fu">},</span></span>
<span id="cb5-34"><a href="#cb5-34" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;Errors&quot;</span><span class="fu">:</span> <span class="fu">{},</span></span>
<span id="cb5-35"><a href="#cb5-35" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;Final tally of cast ballots&quot;</span><span class="fu">:</span> <span class="ot">[</span></span>
<span id="cb5-36"><a href="#cb5-36" aria-hidden="true" tabindex="-1"></a>    <span class="fu">{</span></span>
<span id="cb5-37"><a href="#cb5-37" aria-hidden="true" tabindex="-1"></a>      <span class="dt">&quot;question&quot;</span><span class="fu">:</span> <span class="st">&quot;Should pineapple be banned on pizza?&quot;</span><span class="fu">,</span></span>
<span id="cb5-38"><a href="#cb5-38" aria-hidden="true" tabindex="-1"></a>      <span class="dt">&quot;answers&quot;</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb5-39"><a href="#cb5-39" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;Unsure&quot;</span><span class="fu">:</span> <span class="dv">3</span><span class="fu">,</span></span>
<span id="cb5-40"><a href="#cb5-40" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;No&quot;</span><span class="fu">:</span> <span class="dv">2</span><span class="fu">,</span></span>
<span id="cb5-41"><a href="#cb5-41" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;Yes&quot;</span><span class="fu">:</span> <span class="dv">1</span></span>
<span id="cb5-42"><a href="#cb5-42" aria-hidden="true" tabindex="-1"></a>      <span class="fu">}</span></span>
<span id="cb5-43"><a href="#cb5-43" aria-hidden="true" tabindex="-1"></a>    <span class="fu">}</span></span>
<span id="cb5-44"><a href="#cb5-44" aria-hidden="true" tabindex="-1"></a>  <span class="ot">]</span><span class="fu">,</span></span>
<span id="cb5-45"><a href="#cb5-45" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;Individual spoiled ballots&quot;</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb5-46"><a href="#cb5-46" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;f512d9a0-e023-11f0-8c36-ce89c2e804da&quot;</span><span class="fu">:</span> <span class="ot">[</span></span>
<span id="cb5-47"><a href="#cb5-47" aria-hidden="true" tabindex="-1"></a>      <span class="fu">{</span></span>
<span id="cb5-48"><a href="#cb5-48" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;Should pineapple be banned on pizza?&quot;</span><span class="fu">:</span> <span class="st">&quot;No&quot;</span></span>
<span id="cb5-49"><a href="#cb5-49" aria-hidden="true" tabindex="-1"></a>      <span class="fu">}</span></span>
<span id="cb5-50"><a href="#cb5-50" aria-hidden="true" tabindex="-1"></a>    <span class="ot">]</span><span class="fu">,</span></span>
<span id="cb5-51"><a href="#cb5-51" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;f38097b2-e023-11f0-bd15-faf5e3d58ded&quot;</span><span class="fu">:</span> <span class="ot">[</span></span>
<span id="cb5-52"><a href="#cb5-52" aria-hidden="true" tabindex="-1"></a>      <span class="fu">{</span></span>
<span id="cb5-53"><a href="#cb5-53" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;Should pineapple be banned on pizza?&quot;</span><span class="fu">:</span> <span class="st">&quot;Yes&quot;</span></span>
<span id="cb5-54"><a href="#cb5-54" aria-hidden="true" tabindex="-1"></a>      <span class="fu">}</span></span>
<span id="cb5-55"><a href="#cb5-55" aria-hidden="true" tabindex="-1"></a>    <span class="ot">]</span><span class="fu">,</span></span>
<span id="cb5-56"><a href="#cb5-56" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;f595f4d4-e023-11f0-aea1-faf5e3d58ded&quot;</span><span class="fu">:</span> <span class="ot">[</span></span>
<span id="cb5-57"><a href="#cb5-57" aria-hidden="true" tabindex="-1"></a>      <span class="fu">{</span></span>
<span id="cb5-58"><a href="#cb5-58" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;Should pineapple be banned on pizza?&quot;</span><span class="fu">:</span> <span class="st">&quot;No&quot;</span></span>
<span id="cb5-59"><a href="#cb5-59" aria-hidden="true" tabindex="-1"></a>      <span class="fu">}</span></span>
<span id="cb5-60"><a href="#cb5-60" aria-hidden="true" tabindex="-1"></a>    <span class="ot">]</span><span class="fu">,</span></span>
<span id="cb5-61"><a href="#cb5-61" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;f2f71be0-e023-11f0-8779-ce89c2e804da&quot;</span><span class="fu">:</span> <span class="ot">[</span></span>
<span id="cb5-62"><a href="#cb5-62" aria-hidden="true" tabindex="-1"></a>      <span class="fu">{</span></span>
<span id="cb5-63"><a href="#cb5-63" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;Should pineapple be banned on pizza?&quot;</span><span class="fu">:</span> <span class="st">&quot;Yes&quot;</span></span>
<span id="cb5-64"><a href="#cb5-64" aria-hidden="true" tabindex="-1"></a>      <span class="fu">}</span></span>
<span id="cb5-65"><a href="#cb5-65" aria-hidden="true" tabindex="-1"></a>    <span class="ot">]</span><span class="fu">,</span></span>
<span id="cb5-66"><a href="#cb5-66" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;f408e6e4-e023-11f0-ba1e-2eb40f2ca782&quot;</span><span class="fu">:</span> <span class="ot">[</span></span>
<span id="cb5-67"><a href="#cb5-67" aria-hidden="true" tabindex="-1"></a>      <span class="fu">{</span></span>
<span id="cb5-68"><a href="#cb5-68" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;Should pineapple be banned on pizza?&quot;</span><span class="fu">:</span> <span class="st">&quot;Yes&quot;</span></span>
<span id="cb5-69"><a href="#cb5-69" aria-hidden="true" tabindex="-1"></a>      <span class="fu">}</span></span>
<span id="cb5-70"><a href="#cb5-70" aria-hidden="true" tabindex="-1"></a>    <span class="ot">]</span><span class="fu">,</span></span>
<span id="cb5-71"><a href="#cb5-71" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;f71c771a-e023-11f0-8c68-ce89c2e804da&quot;</span><span class="fu">:</span> <span class="ot">[</span></span>
<span id="cb5-72"><a href="#cb5-72" aria-hidden="true" tabindex="-1"></a>      <span class="fu">{</span></span>
<span id="cb5-73"><a href="#cb5-73" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;Should pineapple be banned on pizza?&quot;</span><span class="fu">:</span> <span class="st">&quot;Unsure&quot;</span></span>
<span id="cb5-74"><a href="#cb5-74" aria-hidden="true" tabindex="-1"></a>      <span class="fu">}</span></span>
<span id="cb5-75"><a href="#cb5-75" aria-hidden="true" tabindex="-1"></a>    <span class="ot">]</span></span>
<span id="cb5-76"><a href="#cb5-76" aria-hidden="true" tabindex="-1"></a>  <span class="fu">}</span></span>
<span id="cb5-77"><a href="#cb5-77" aria-hidden="true" tabindex="-1"></a><span class="fu">}</span></span></code></pre></div>
<h1 id="design">Design</h1>
<p>I implemented the verifier as a DAG (directed acyclic graph) of Python functions to make it easier to extend and run incrementally.
The idea is that when one artifact fails to verify, that failure should spread to any other checks that depend on it, but we should also still verify as many properties of the election as we can without it.</p>
<h2 id="dag">DAG</h2>
<p><a href="./deps.svg"><img src="deps.svg"></img></a></p>
<p>The colors are a little bit idiosyncratic:</p>
<ul>
<li>orange is a “regular” node that checks one artifact.</li>
<li>blue is a map/list node that checks all the orange nodes of a particular type</li>
<li>blue is a trivial “gather” node, like a phony Makefile target, to make the log print nicely</li>
<li>yellow is one of the nodes we’ll look at in more detail in the next section</li>
</ul>
<h2 id="verify_-functions"><code>verify_</code> functions</h2>
<p>Each node corresponds to a function <code>verify_&lt;node name&gt;</code>
Let’s look at the code for the first yellow one above.
It prints this part of the log:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode txt"><code class="sourceCode default"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a>Verifying ballot ID sets:</span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a>✅ 6 ballots spoiled = 6 ballots decrypted</span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a>✅ 6 ballots cast + 6 ballots spoiled = 12 ballots submitted</span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a>✅ set(spoiled ballot IDs) = set(decrypted ballot IDs)</span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a>✅ set(cast ballot IDs) + set(spoiled ballot IDs) = set(submitted ballot IDs)</span></code></pre></div>
<div class="sourceCode" id="cb7"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> verify_ballot_sets(results, pubdir, log) <span class="op">-&gt;</span> <span class="bu">bool</span>:</span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a>    <span class="co">&quot;Make sure the various sets of ballot IDs match up (nothing missing or extra)&quot;</span></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a>    log.info(<span class="st">&#39;</span><span class="ch">\n</span><span class="st">Verifying ballot ID sets:&#39;</span>)</span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a>    deps <span class="op">=</span> verify_deps(</span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a>        n_spoiled_decrypted        <span class="op">=</span> verify(results, pubdir, log, <span class="st">&#39;n_spoiled_decrypted&#39;</span>),</span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a>        n_cast_spoiled_submitted   <span class="op">=</span> verify(results, pubdir, log, <span class="st">&#39;n_cast_spoiled_submitted&#39;</span>),</span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a>        set_spoiled_decrypted      <span class="op">=</span> verify(results, pubdir, log, <span class="st">&#39;set_spoiled_decrypted&#39;</span>),</span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a>        set_cast_spoiled_submitted <span class="op">=</span> verify(results, pubdir, log, <span class="st">&#39;set_cast_spoiled_submitted&#39;</span>),</span>
<span id="cb7-9"><a href="#cb7-9" aria-hidden="true" tabindex="-1"></a>    )</span>
<span id="cb7-10"><a href="#cb7-10" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> <span class="va">True</span></span></code></pre></div>
<p>Besides the function per node, there are two special “verify” functions:</p>
<ul>
<li><p>When a node depends on other checks to run first, it should load their results via <code>verify_deps</code>.
That makes sure that all dependencies verify and short circuits the rest of the current function if one of them fails.</p></li>
<li><p>Within <code>verify_deps</code>, each dependency should be checked via <code>verify</code>.
That caches results so each check only runs once.</p></li>
</ul>
<p>Some functions correspond directly to a section of the log, but some don’t.
For example, <code>verify_tally_aggregation</code> only prints one line:</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> verify_tally_aggregation(results, pubdir, log):</span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a>    deps <span class="op">=</span> verify_deps(</span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a>        manifest <span class="op">=</span> verify(results, pubdir, log, <span class="st">&#39;manifest&#39;</span>),</span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a>        context <span class="op">=</span> verify(results, pubdir, log, <span class="st">&#39;context&#39;</span>),</span>
<span id="cb8-6"><a href="#cb8-6" aria-hidden="true" tabindex="-1"></a>        all_ballots_cast <span class="op">=</span> verify(results, pubdir, log, <span class="st">&#39;all_ballots_cast&#39;</span>),</span>
<span id="cb8-7"><a href="#cb8-7" aria-hidden="true" tabindex="-1"></a>        ciphertext_tally <span class="op">=</span> verify(results, pubdir, log, <span class="st">&#39;ciphertext_tally&#39;</span>),</span>
<span id="cb8-8"><a href="#cb8-8" aria-hidden="true" tabindex="-1"></a>    )   </span>
<span id="cb8-9"><a href="#cb8-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-10"><a href="#cb8-10" aria-hidden="true" tabindex="-1"></a>    n_cast <span class="op">=</span> <span class="bu">len</span>(deps[<span class="st">&#39;all_ballots_cast&#39;</span>])</span>
<span id="cb8-11"><a href="#cb8-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-12"><a href="#cb8-12" aria-hidden="true" tabindex="-1"></a>    <span class="kw">def</span> verify_closure():</span>
<span id="cb8-13"><a href="#cb8-13" aria-hidden="true" tabindex="-1"></a>        new_tally <span class="op">=</span> CiphertextTally(</span>
<span id="cb8-14"><a href="#cb8-14" aria-hidden="true" tabindex="-1"></a>            <span class="st">&quot;verify-tally&quot;</span>,</span>
<span id="cb8-15"><a href="#cb8-15" aria-hidden="true" tabindex="-1"></a>            InternalManifest(deps[<span class="st">&#39;manifest&#39;</span>]),</span>
<span id="cb8-16"><a href="#cb8-16" aria-hidden="true" tabindex="-1"></a>            deps[<span class="st">&#39;context&#39;</span>]</span>
<span id="cb8-17"><a href="#cb8-17" aria-hidden="true" tabindex="-1"></a>        )   </span>
<span id="cb8-18"><a href="#cb8-18" aria-hidden="true" tabindex="-1"></a>        <span class="cf">for</span> ballot <span class="kw">in</span> deps[<span class="st">&#39;all_ballots_cast&#39;</span>]:</span>
<span id="cb8-19"><a href="#cb8-19" aria-hidden="true" tabindex="-1"></a>            <span class="cf">assert</span>(new_tally.append(ballot, should_validate<span class="op">=</span><span class="va">True</span>))</span>
<span id="cb8-20"><a href="#cb8-20" aria-hidden="true" tabindex="-1"></a>        <span class="cf">assert</span> new_tally.contests <span class="op">==</span> deps[<span class="st">&#39;ciphertext_tally&#39;</span>].contests</span>
<span id="cb8-21"><a href="#cb8-21" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-22"><a href="#cb8-22" aria-hidden="true" tabindex="-1"></a>    with_checkmark_message(</span>
<span id="cb8-23"><a href="#cb8-23" aria-hidden="true" tabindex="-1"></a>        <span class="ss">f&#39;ciphertext_tally is the correct aggregation of the </span><span class="sc">{</span>n_cast<span class="sc">}</span><span class="ss"> cast ballots&#39;</span>,</span>
<span id="cb8-24"><a href="#cb8-24" aria-hidden="true" tabindex="-1"></a>        verify_closure,</span>
<span id="cb8-25"><a href="#cb8-25" aria-hidden="true" tabindex="-1"></a>        log </span>
<span id="cb8-26"><a href="#cb8-26" aria-hidden="true" tabindex="-1"></a>    )   </span>
<span id="cb8-27"><a href="#cb8-27" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-28"><a href="#cb8-28" aria-hidden="true" tabindex="-1"></a>    <span class="cf">return</span> <span class="va">True</span></span></code></pre></div>
<div class="sourceCode" id="cb9"><pre class="sourceCode txt"><code class="sourceCode default"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a>✅ ciphertext_tally is the correct aggregation of the 6 cast ballots</span></code></pre></div>
<p>That’s because by the time it gets called in <code>main</code>,
all the dependencies have already been checked.</p>
<div class="sourceCode" id="cb10"><pre class="sourceCode python"><code class="sourceCode python"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="kw">def</span> main(pubdir, log, verifier_id):</span>
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a>    <span class="co"># main program state</span></span>
<span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a>    <span class="co"># accumulates successful result objects and error messages</span></span>
<span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a>    results: ResultsCache <span class="op">=</span> {}</span>
<span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a>    </span>
<span id="cb10-7"><a href="#cb10-7" aria-hidden="true" tabindex="-1"></a>    <span class="co"># these partially overlap, which is fine</span></span>
<span id="cb10-8"><a href="#cb10-8" aria-hidden="true" tabindex="-1"></a>    verify(results, pubdir, log, <span class="st">&#39;gather_config&#39;</span>)</span>
<span id="cb10-9"><a href="#cb10-9" aria-hidden="true" tabindex="-1"></a>    verify(results, pubdir, log, <span class="st">&#39;all_ballots_submitted&#39;</span>)</span>
<span id="cb10-10"><a href="#cb10-10" aria-hidden="true" tabindex="-1"></a>    verify(results, pubdir, log, <span class="st">&#39;all_ballots_cast&#39;</span>)</span>
<span id="cb10-11"><a href="#cb10-11" aria-hidden="true" tabindex="-1"></a>    verify(results, pubdir, log, <span class="st">&#39;all_ballots_spoiled&#39;</span>)</span>
<span id="cb10-12"><a href="#cb10-12" aria-hidden="true" tabindex="-1"></a>    verify(results, pubdir, log, <span class="st">&#39;all_spoiled_results&#39;</span>)</span>
<span id="cb10-13"><a href="#cb10-13" aria-hidden="true" tabindex="-1"></a>    verify(results, pubdir, log, <span class="st">&#39;ballot_sets&#39;</span>)</span>
<span id="cb10-14"><a href="#cb10-14" aria-hidden="true" tabindex="-1"></a>    verify(results, pubdir, log, <span class="st">&#39;gather_tally&#39;</span>)</span>
<span id="cb10-15"><a href="#cb10-15" aria-hidden="true" tabindex="-1"></a>    verify(results, pubdir, log, <span class="st">&#39;gather_decryptions&#39;</span>)</span>
<span id="cb10-16"><a href="#cb10-16" aria-hidden="true" tabindex="-1"></a>    verify(results, pubdir, log, <span class="st">&#39;gather_election&#39;</span>)</span>
<span id="cb10-17"><a href="#cb10-17" aria-hidden="true" tabindex="-1"></a>    </span>
<span id="cb10-18"><a href="#cb10-18" aria-hidden="true" tabindex="-1"></a>    (successes, errors, bools) <span class="op">=</span> simplify_and_partition(results, log)</span>
<span id="cb10-19"><a href="#cb10-19" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb10-20"><a href="#cb10-20" aria-hidden="true" tabindex="-1"></a>    summarize_results(</span>
<span id="cb10-21"><a href="#cb10-21" aria-hidden="true" tabindex="-1"></a>        successes, errors, bools,</span>
<span id="cb10-22"><a href="#cb10-22" aria-hidden="true" tabindex="-1"></a>        pubdir, log, verifier_id,</span>
<span id="cb10-23"><a href="#cb10-23" aria-hidden="true" tabindex="-1"></a>    )</span></code></pre></div>
<h1 id="can-we-break-it">Can we break it?</h1>
<p>One more thing for today: what happens if the artifacts aren’t all valid?
Here’s an example of it failing because I manually removed one of the submitted ballots:</p>
<div class="sourceCode" id="cb11"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> ./verify.py</span>
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a><span class="co"># ... mostly same output as above ...</span></span>
<span id="cb11-4"><a href="#cb11-4" aria-hidden="true" tabindex="-1"></a><span class="co"># ...</span></span>
<span id="cb11-5"><a href="#cb11-5" aria-hidden="true" tabindex="-1"></a><span class="ex">❌</span> ballot_submitted {<span class="st">&#39;ballot_id&#39;</span>: <span class="st">&#39;ballot-f2f71be0-e023-11f0-8779-ce89c2e804da&#39;</span>}</span>
<span id="cb11-6"><a href="#cb11-6" aria-hidden="true" tabindex="-1"></a><span class="co"># ...</span></span>
<span id="cb11-7"><a href="#cb11-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-8"><a href="#cb11-8" aria-hidden="true" tabindex="-1"></a><span class="ex">----------------------------------------</span></span>
<span id="cb11-9"><a href="#cb11-9" aria-hidden="true" tabindex="-1"></a><span class="ex">Final</span> tally of cast ballots</span>
<span id="cb11-10"><a href="#cb11-10" aria-hidden="true" tabindex="-1"></a><span class="ex">----------------------------------------</span></span>
<span id="cb11-11"><a href="#cb11-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-12"><a href="#cb11-12" aria-hidden="true" tabindex="-1"></a><span class="ex">Should</span> pineapple be banned on pizza<span class="pp">?</span></span>
<span id="cb11-13"><a href="#cb11-13" aria-hidden="true" tabindex="-1"></a>  <span class="ex">Yes:</span> 3</span>
<span id="cb11-14"><a href="#cb11-14" aria-hidden="true" tabindex="-1"></a>  <span class="ex">No:</span> 2</span>
<span id="cb11-15"><a href="#cb11-15" aria-hidden="true" tabindex="-1"></a>  <span class="ex">Unsure:</span> 1</span>
<span id="cb11-16"><a href="#cb11-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb11-17"><a href="#cb11-17" aria-hidden="true" tabindex="-1"></a><span class="ex">⛔</span> The election could NOT be verified!</span>
<span id="cb11-18"><a href="#cb11-18" aria-hidden="true" tabindex="-1"></a><span class="ex">⛔</span> There were 9 errors.</span>
<span id="cb11-19"><a href="#cb11-19" aria-hidden="true" tabindex="-1"></a><span class="ex">⛔</span> See verifier1.json for details.</span></code></pre></div>
<div class="sourceCode" id="cb12"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> cat ../election/data/public/4_verify/verifier1.json <span class="kw">|</span> <span class="ex">jq</span></span></code></pre></div>
<div class="sourceCode" id="cb13"><pre class="sourceCode json"><code class="sourceCode json"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a><span class="fu">{</span></span>
<span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;Verified&quot;</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb13-3"><a href="#cb13-3" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;manifest&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb13-4"><a href="#cb13-4" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;ceremony_details&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb13-5"><a href="#cb13-5" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;gather_announce&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb13-6"><a href="#cb13-6" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;all_guardian_backups&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb13-7"><a href="#cb13-7" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;all_guardian_verifications&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb13-8"><a href="#cb13-8" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;gather_ceremony&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb13-9"><a href="#cb13-9" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;joint_key&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb13-10"><a href="#cb13-10" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;build_election&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb13-11"><a href="#cb13-11" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;constants&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb13-12"><a href="#cb13-12" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;internal_manifest&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb13-13"><a href="#cb13-13" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;context&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb13-14"><a href="#cb13-14" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;gather_constants&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb13-15"><a href="#cb13-15" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;all_devices&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb13-16"><a href="#cb13-16" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;gather_config&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb13-17"><a href="#cb13-17" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;all_ballots_submitted&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb13-18"><a href="#cb13-18" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;all_ballots_cast&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb13-19"><a href="#cb13-19" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;all_ballots_spoiled&quot;</span><span class="fu">:</span> <span class="kw">false</span><span class="fu">,</span></span>
<span id="cb13-20"><a href="#cb13-20" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;all_spoiled_results&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb13-21"><a href="#cb13-21" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;n_spoiled_decrypted&quot;</span><span class="fu">:</span> <span class="kw">false</span><span class="fu">,</span></span>
<span id="cb13-22"><a href="#cb13-22" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;n_cast_spoiled_submitted&quot;</span><span class="fu">:</span> <span class="kw">false</span><span class="fu">,</span></span>
<span id="cb13-23"><a href="#cb13-23" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;set_spoiled_decrypted&quot;</span><span class="fu">:</span> <span class="kw">false</span><span class="fu">,</span></span>
<span id="cb13-24"><a href="#cb13-24" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;set_cast_spoiled_submitted&quot;</span><span class="fu">:</span> <span class="kw">false</span><span class="fu">,</span></span>
<span id="cb13-25"><a href="#cb13-25" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;ballot_sets&quot;</span><span class="fu">:</span> <span class="kw">false</span><span class="fu">,</span></span>
<span id="cb13-26"><a href="#cb13-26" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;ciphertext_tally&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb13-27"><a href="#cb13-27" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;tally_aggregation&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb13-28"><a href="#cb13-28" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;plaintext_tally&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb13-29"><a href="#cb13-29" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;tally_decryption&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb13-30"><a href="#cb13-30" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;gather_tally&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb13-31"><a href="#cb13-31" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;gather_decryptions&quot;</span><span class="fu">:</span> <span class="kw">true</span><span class="fu">,</span></span>
<span id="cb13-32"><a href="#cb13-32" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;gather_election&quot;</span><span class="fu">:</span> <span class="kw">false</span></span>
<span id="cb13-33"><a href="#cb13-33" aria-hidden="true" tabindex="-1"></a>  <span class="fu">},</span></span>
<span id="cb13-34"><a href="#cb13-34" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;Errors&quot;</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb13-35"><a href="#cb13-35" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;ballot_submitted&quot;</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb13-36"><a href="#cb13-36" aria-hidden="true" tabindex="-1"></a>      <span class="dt">&quot;ballot-f2f71be0-e023-11f0-8779-ce89c2e804da&quot;</span><span class="fu">:</span> <span class="st">&quot;[Errno 2] No such file or directory: &#39;/data/public/2_ballots/1_submitted/ballot-f2f71be0-e023-11f0-8779-ce89c2e804da.json&#39;&quot;</span></span>
<span id="cb13-37"><a href="#cb13-37" aria-hidden="true" tabindex="-1"></a>    <span class="fu">},</span></span>
<span id="cb13-38"><a href="#cb13-38" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;ballot_spoiled&quot;</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb13-39"><a href="#cb13-39" aria-hidden="true" tabindex="-1"></a>      <span class="dt">&quot;ballot-f2f71be0-e023-11f0-8779-ce89c2e804da&quot;</span><span class="fu">:</span> <span class="st">&quot;dependencies failed: ballot_submitted&quot;</span></span>
<span id="cb13-40"><a href="#cb13-40" aria-hidden="true" tabindex="-1"></a>    <span class="fu">},</span></span>
<span id="cb13-41"><a href="#cb13-41" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;all_ballots_spoiled&quot;</span><span class="fu">:</span> <span class="st">&quot;dependencies failed: ballot-f2f71be0-e023-11f0-8779-ce89c2e804da&quot;</span><span class="fu">,</span></span>
<span id="cb13-42"><a href="#cb13-42" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;n_spoiled_decrypted&quot;</span><span class="fu">:</span> <span class="st">&quot;dependencies failed: all_ballots_spoiled&quot;</span><span class="fu">,</span></span>
<span id="cb13-43"><a href="#cb13-43" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;n_cast_spoiled_submitted&quot;</span><span class="fu">:</span> <span class="st">&quot;dependencies failed: all_ballots_spoiled&quot;</span><span class="fu">,</span></span>
<span id="cb13-44"><a href="#cb13-44" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;set_spoiled_decrypted&quot;</span><span class="fu">:</span> <span class="st">&quot;dependencies failed: all_ballots_spoiled&quot;</span><span class="fu">,</span></span>
<span id="cb13-45"><a href="#cb13-45" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;set_cast_spoiled_submitted&quot;</span><span class="fu">:</span> <span class="st">&quot;dependencies failed: all_ballots_spoiled&quot;</span><span class="fu">,</span></span>
<span id="cb13-46"><a href="#cb13-46" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;ballot_sets&quot;</span><span class="fu">:</span> <span class="st">&quot;dependencies failed: n_cast_spoiled_submitted, n_spoiled_decrypted, set_cast_spoiled_submitted, set_spoiled_decrypted&quot;</span><span class="fu">,</span></span>
<span id="cb13-47"><a href="#cb13-47" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;gather_election&quot;</span><span class="fu">:</span> <span class="st">&quot;dependencies failed: ballot_sets&quot;</span></span>
<span id="cb13-48"><a href="#cb13-48" aria-hidden="true" tabindex="-1"></a>  <span class="fu">},</span></span>
<span id="cb13-49"><a href="#cb13-49" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;Final tally of cast ballots&quot;</span><span class="fu">:</span> <span class="ot">[</span></span>
<span id="cb13-50"><a href="#cb13-50" aria-hidden="true" tabindex="-1"></a>    <span class="fu">{</span></span>
<span id="cb13-51"><a href="#cb13-51" aria-hidden="true" tabindex="-1"></a>      <span class="dt">&quot;question&quot;</span><span class="fu">:</span> <span class="st">&quot;Should pineapple be banned on pizza?&quot;</span><span class="fu">,</span></span>
<span id="cb13-52"><a href="#cb13-52" aria-hidden="true" tabindex="-1"></a>      <span class="dt">&quot;answers&quot;</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb13-53"><a href="#cb13-53" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;Unsure&quot;</span><span class="fu">:</span> <span class="dv">3</span><span class="fu">,</span></span>
<span id="cb13-54"><a href="#cb13-54" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;No&quot;</span><span class="fu">:</span> <span class="dv">2</span><span class="fu">,</span></span>
<span id="cb13-55"><a href="#cb13-55" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;Yes&quot;</span><span class="fu">:</span> <span class="dv">1</span></span>
<span id="cb13-56"><a href="#cb13-56" aria-hidden="true" tabindex="-1"></a>      <span class="fu">}</span></span>
<span id="cb13-57"><a href="#cb13-57" aria-hidden="true" tabindex="-1"></a>    <span class="fu">}</span></span>
<span id="cb13-58"><a href="#cb13-58" aria-hidden="true" tabindex="-1"></a>  <span class="ot">]</span><span class="fu">,</span></span>
<span id="cb13-59"><a href="#cb13-59" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;Individual spoiled ballots&quot;</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb13-60"><a href="#cb13-60" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;f512d9a0-e023-11f0-8c36-ce89c2e804da&quot;</span><span class="fu">:</span> <span class="ot">[</span></span>
<span id="cb13-61"><a href="#cb13-61" aria-hidden="true" tabindex="-1"></a>      <span class="fu">{</span></span>
<span id="cb13-62"><a href="#cb13-62" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;Should pineapple be banned on pizza?&quot;</span><span class="fu">:</span> <span class="st">&quot;No&quot;</span></span>
<span id="cb13-63"><a href="#cb13-63" aria-hidden="true" tabindex="-1"></a>      <span class="fu">}</span></span>
<span id="cb13-64"><a href="#cb13-64" aria-hidden="true" tabindex="-1"></a>    <span class="ot">]</span><span class="fu">,</span></span>
<span id="cb13-65"><a href="#cb13-65" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;f38097b2-e023-11f0-bd15-faf5e3d58ded&quot;</span><span class="fu">:</span> <span class="ot">[</span></span>
<span id="cb13-66"><a href="#cb13-66" aria-hidden="true" tabindex="-1"></a>      <span class="fu">{</span></span>
<span id="cb13-67"><a href="#cb13-67" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;Should pineapple be banned on pizza?&quot;</span><span class="fu">:</span> <span class="st">&quot;Yes&quot;</span></span>
<span id="cb13-68"><a href="#cb13-68" aria-hidden="true" tabindex="-1"></a>      <span class="fu">}</span></span>
<span id="cb13-69"><a href="#cb13-69" aria-hidden="true" tabindex="-1"></a>    <span class="ot">]</span><span class="fu">,</span></span>
<span id="cb13-70"><a href="#cb13-70" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;f595f4d4-e023-11f0-aea1-faf5e3d58ded&quot;</span><span class="fu">:</span> <span class="ot">[</span></span>
<span id="cb13-71"><a href="#cb13-71" aria-hidden="true" tabindex="-1"></a>      <span class="fu">{</span></span>
<span id="cb13-72"><a href="#cb13-72" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;Should pineapple be banned on pizza?&quot;</span><span class="fu">:</span> <span class="st">&quot;No&quot;</span></span>
<span id="cb13-73"><a href="#cb13-73" aria-hidden="true" tabindex="-1"></a>      <span class="fu">}</span></span>
<span id="cb13-74"><a href="#cb13-74" aria-hidden="true" tabindex="-1"></a>    <span class="ot">]</span><span class="fu">,</span></span>
<span id="cb13-75"><a href="#cb13-75" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;f2f71be0-e023-11f0-8779-ce89c2e804da&quot;</span><span class="fu">:</span> <span class="ot">[</span></span>
<span id="cb13-76"><a href="#cb13-76" aria-hidden="true" tabindex="-1"></a>      <span class="fu">{</span></span>
<span id="cb13-77"><a href="#cb13-77" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;Should pineapple be banned on pizza?&quot;</span><span class="fu">:</span> <span class="st">&quot;Yes&quot;</span></span>
<span id="cb13-78"><a href="#cb13-78" aria-hidden="true" tabindex="-1"></a>      <span class="fu">}</span></span>
<span id="cb13-79"><a href="#cb13-79" aria-hidden="true" tabindex="-1"></a>    <span class="ot">]</span><span class="fu">,</span></span>
<span id="cb13-80"><a href="#cb13-80" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;f408e6e4-e023-11f0-ba1e-2eb40f2ca782&quot;</span><span class="fu">:</span> <span class="ot">[</span></span>
<span id="cb13-81"><a href="#cb13-81" aria-hidden="true" tabindex="-1"></a>      <span class="fu">{</span></span>
<span id="cb13-82"><a href="#cb13-82" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;Should pineapple be banned on pizza?&quot;</span><span class="fu">:</span> <span class="st">&quot;Yes&quot;</span></span>
<span id="cb13-83"><a href="#cb13-83" aria-hidden="true" tabindex="-1"></a>      <span class="fu">}</span></span>
<span id="cb13-84"><a href="#cb13-84" aria-hidden="true" tabindex="-1"></a>    <span class="ot">]</span><span class="fu">,</span></span>
<span id="cb13-85"><a href="#cb13-85" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;f71c771a-e023-11f0-8c68-ce89c2e804da&quot;</span><span class="fu">:</span> <span class="ot">[</span></span>
<span id="cb13-86"><a href="#cb13-86" aria-hidden="true" tabindex="-1"></a>      <span class="fu">{</span></span>
<span id="cb13-87"><a href="#cb13-87" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;Should pineapple be banned on pizza?&quot;</span><span class="fu">:</span> <span class="st">&quot;Unsure&quot;</span></span>
<span id="cb13-88"><a href="#cb13-88" aria-hidden="true" tabindex="-1"></a>      <span class="fu">}</span></span>
<span id="cb13-89"><a href="#cb13-89" aria-hidden="true" tabindex="-1"></a>    <span class="ot">]</span></span>
<span id="cb13-90"><a href="#cb13-90" aria-hidden="true" tabindex="-1"></a>  <span class="fu">}</span></span>
<span id="cb13-91"><a href="#cb13-91" aria-hidden="true" tabindex="-1"></a><span class="fu">}</span></span></code></pre></div>
<p>Because the ballot I removed turned out to be spoiled rather than cast, the final tally <em>is</em> valid and verifies normally. We want that, because otherwise it would be possible to hold up the entire election by messing with any single ballot and that could become a DDoS vector. But it also loudly warns which DAG nodes are invalid: the submitted ballot I removed, the corresponding spoiled one, and then all the nodes that depend on the list of spoiled ballots.</p>
<p>That’s all for now! If you want to play with breaking it in interesting ways
I suggest starting from the <a href="/posts/2025/12/23/egc-dev03-election-tests">dev update #3</a> code instead.
It includes a test framework where you can implement custom attack functions.</p>]]></summary>
</entry>
<entry>
    <title>ElectionGuard + Cardano Dev Update #1: Election Demo</title>
    <link href="https://cryptoisland.blog/posts/2025/12/23/egc-dev01-election-demo" />
    <id>https://cryptoisland.blog/posts/2025/12/23/egc-dev01-election-demo</id>
    <published>2025-12-23T00:00:00Z</published>
    <updated>2025-12-23T00:00:00Z</updated>
    <summary type="html"><![CDATA[
<div class="toc"><div class="header">Contents</div>
<ul>
<li><a href="#architecture" id="toc-architecture">Architecture</a></li>
<li><a href="#the-pineapple-referendum" id="toc-the-pineapple-referendum">The Pineapple Referendum</a></li>
<li><a href="#dev-options" id="toc-dev-options">Dev options</a></li>
</ul>
<center class="reminder"><img src="pineapple.png"></img></center></div>
<p>Today’s code runs an ElectionGuard election locally using a set of Docker containers.
It comes with a companion <a href="https://www.youtube.com/watch?v=wQx7ZbuKT7o">YouTube video</a> and <a href="https://asciinema.org/a/lloCjFW2LvnqdqKEbQGdySsRC">Asciinema demo</a>.</p>
<p>Since this is the first update, here are some other project resources too:</p>
<ul>
<li><a href="https://github.com/jefdaj/electionguard-cardano/tree/trunk/milestone1/election">jefdaj/electionguard-cardano</a> is the main codebase</li>
<li><a href="https://github.com/jefdaj/electionguard-python">jefdaj/electionguard-python</a> is my fork of the EG reference implementation</li>
<li><a href="https://milestones.projectcatalyst.io/projects/1300090">The Catalyst project page</a></li>
</ul>
<!--
TODO include a few "why"s:
- why docker containers instead of nix only?
- why arion instead of docker-compose?
- why the json file arion/python thing?
-->
<h1 id="architecture">Architecture</h1>
<p>This is my rough plan for building out the demo:</p>
<p><img src="stages.png" /></p>
<p>Stage 1 is a Docker container packaging the reference implementation, which <a href="https://github.com/jefdaj/electionguard-python">I did already</a>.</p>
<p>Today’s demo is the first part of stage 2.
It’s a top-level Python script controlling a set of Docker containers.
<a href="https://github.com/jefdaj/electionguard-cardano/tree/trunk/milestone1/election/election.py">election.py</a> reads <a href="https://github.com/jefdaj/electionguard-cardano/tree/trunk/milestone1/election/election.json">election.json</a>,
spins up the containers using Arion,
and runs <code>docker exec</code> commands telling them what to do at each step:</p>
<p><img src="stage2-nolaptop.png" /></p>
<p>The containers each have a private state in <code>data/private/&lt;container name&gt;</code>, and they communicate only via a shared <code>data/public</code> folder. For now we’ll just pretend it enforces the rules of a blockchain: files in there are timestamped, immutable, and digitally signed by the wallet of the party that added them. In milestone 2 (confusingly referred to as stage 3 in the diagram above) I’ll work on publishing these files over Cardano + IPFS.</p>
<p>I chose to set it up with Docker containers this way rather than my initial thought (pure Nix) for three main reasons:</p>
<ol type="1">
<li>It’ll make adding other components like Cardano, IPFS, Ogmios, Kupo easier</li>
<li>It’ll make the system easier to set up for technical users who want to host their own elections</li>
<li>The reference implementation depends on very specific library versions that I had trouble satisfying using anything other than Ubuntu or Debian from around the time it was published.</li>
</ol>
<h1 id="the-pineapple-referendum">The Pineapple Referendum</h1>
<p>This is a minimal ballot for testing purposes with only one question.
The options in the reference implementation were “yes” and “no”,
but I added an “unsure” option to check that I understand how to change them.
(It’s a bit of a hack and involves making each answer a “candidate”.)</p>
<p><a href="https://github.com/jefdaj/electionguard-cardano/tree/trunk/milestone1/election/election.json">election.json</a> scripts the number of votes for each option,
so we can check at the end that the decrypted tally matches.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode json"><code class="sourceCode json"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="fu">{</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;arion&quot;</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;project_name&quot;</span><span class="fu">:</span> <span class="st">&quot;election&quot;</span><span class="fu">,</span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;bind_mounts&quot;</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>      <span class="dt">&quot;scripts&quot;</span><span class="fu">:</span> <span class="st">&quot;/scripts&quot;</span><span class="fu">,</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>      <span class="dt">&quot;public&quot;</span><span class="fu">:</span> <span class="st">&quot;/data/public&quot;</span><span class="fu">,</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>      <span class="dt">&quot;private&quot;</span><span class="fu">:</span> <span class="st">&quot;/data/private&quot;</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>    <span class="fu">}</span></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>  <span class="fu">},</span></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;election&quot;</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;guardians&quot;</span><span class="fu">:</span> <span class="fu">{</span><span class="dt">&quot;count&quot;</span><span class="fu">:</span> <span class="dv">3</span><span class="fu">,</span> <span class="dt">&quot;quorum&quot;</span><span class="fu">:</span> <span class="dv">2</span><span class="fu">},</span></span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;devices&quot;</span><span class="fu">:</span> <span class="fu">{</span><span class="dt">&quot;count&quot;</span><span class="fu">:</span> <span class="dv">4</span><span class="fu">},</span></span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;question&quot;</span><span class="fu">:</span> <span class="st">&quot;Should pineapple be banned on pizza?&quot;</span></span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a>  <span class="fu">},</span></span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;votes&quot;</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;Yes&quot;</span><span class="fu">:</span>    <span class="fu">{</span><span class="dt">&quot;cast&quot;</span><span class="fu">:</span> <span class="dv">1</span><span class="fu">,</span> <span class="dt">&quot;spoil&quot;</span><span class="fu">:</span><span class="dv">3</span><span class="fu">},</span></span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;No&quot;</span><span class="fu">:</span>     <span class="fu">{</span><span class="dt">&quot;cast&quot;</span><span class="fu">:</span> <span class="dv">2</span><span class="fu">,</span> <span class="dt">&quot;spoil&quot;</span><span class="fu">:</span><span class="dv">2</span><span class="fu">},</span></span>
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true" tabindex="-1"></a>    <span class="dt">&quot;Unsure&quot;</span><span class="fu">:</span> <span class="fu">{</span><span class="dt">&quot;cast&quot;</span><span class="fu">:</span> <span class="dv">3</span><span class="fu">,</span> <span class="dt">&quot;spoil&quot;</span><span class="fu">:</span><span class="dv">1</span><span class="fu">}</span></span>
<span id="cb1-19"><a href="#cb1-19" aria-hidden="true" tabindex="-1"></a>  <span class="fu">}</span></span>
<span id="cb1-20"><a href="#cb1-20" aria-hidden="true" tabindex="-1"></a><span class="fu">}</span></span></code></pre></div>
<p>Assuming you have Docker and Nix installed,
and Nix flakes enabled, you can run the referendum.</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="bu">cd</span> electionguard-cardano/milestone1/election</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="ex">nix</span> develop</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="ex">./election.sh</span></span></code></pre></div>
<p>When it asks for your password, that’s only to delete <code>./data</code> and possibly to enable Docker commands.
Then it should run a bunch of <code>docker exec</code> commands, like this:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode txt"><code class="sourceCode default"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a>### setup ###</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a>/nix/store/0hnya01zxwfsnd30l8lb9aiqqg2in27a-docker-compose.yaml</span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a> Network election  Creating</span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a> Network election  Created</span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a> Container election-guardian1-1  Creating</span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a> ...</span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a> Container election-guardian1-1  Started</span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a>### build_manifest ###</span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a>docker exec election-admin1-1 poetry run /scripts/admin.py build-manifest --public-dir /data/public --referendum-question Should pineapple be banned on pizza?</span>
<span id="cb3-13"><a href="#cb3-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-14"><a href="#cb3-14" aria-hidden="true" tabindex="-1"></a>### announce_key_ceremony ###</span>
<span id="cb3-15"><a href="#cb3-15" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-16"><a href="#cb3-16" aria-hidden="true" tabindex="-1"></a>docker exec election-admin1-1 poetry run /scripts/admin.py announce-key-ceremony --public-dir /data/public --guardian-count 3 --guardian-quorum 2</span>
<span id="cb3-17"><a href="#cb3-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-18"><a href="#cb3-18" aria-hidden="true" tabindex="-1"></a>### key_ceremony_round1 ###</span>
<span id="cb3-19"><a href="#cb3-19" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-20"><a href="#cb3-20" aria-hidden="true" tabindex="-1"></a>docker exec election-guardian1-1 poetry run /scripts/guardian.py key-ceremony --public-dir /data/public --private-dir /data/private --ceremony-round 1 --guardian-id guardian_1 --guardian-sequence-order 1</span>
<span id="cb3-21"><a href="#cb3-21" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-22"><a href="#cb3-22" aria-hidden="true" tabindex="-1"></a>docker exec election-guardian2-1 poetry run /scripts/guardian.py key-ceremony --public-dir /data/public --private-dir /data/private --ceremony-round 1 --guardian-id guardian_2 --guardian-sequence-order 2</span>
<span id="cb3-23"><a href="#cb3-23" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-24"><a href="#cb3-24" aria-hidden="true" tabindex="-1"></a>docker exec election-guardian3-1 poetry run /scripts/guardian.py key-ceremony --public-dir /data/public --private-dir /data/private --ceremony-round 1 --guardian-id guardian_3 --guardian-sequence-order 3</span>
<span id="cb3-25"><a href="#cb3-25" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-26"><a href="#cb3-26" aria-hidden="true" tabindex="-1"></a>### key_ceremony_round2 ###</span>
<span id="cb3-27"><a href="#cb3-27" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-28"><a href="#cb3-28" aria-hidden="true" tabindex="-1"></a>docker exec election-guardian1-1 poetry run /scripts/guardian.py key-ceremony --public-dir /data/public --private-dir /data/private --ceremony-round 2 --guardian-id guardian_1 --guardian-sequence-order 1</span>
<span id="cb3-29"><a href="#cb3-29" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-30"><a href="#cb3-30" aria-hidden="true" tabindex="-1"></a>docker exec election-guardian2-1 poetry run /scripts/guardian.py key-ceremony --public-dir /data/public --private-dir /data/private --ceremony-round 2 --guardian-id guardian_2 --guardian-sequence-order 2</span>
<span id="cb3-31"><a href="#cb3-31" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-32"><a href="#cb3-32" aria-hidden="true" tabindex="-1"></a>docker exec election-guardian3-1 poetry run /scripts/guardian.py key-ceremony --public-dir /data/public --private-dir /data/private --ceremony-round 2 --guardian-id guardian_3 --guardian-sequence-order 3</span>
<span id="cb3-33"><a href="#cb3-33" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-34"><a href="#cb3-34" aria-hidden="true" tabindex="-1"></a>### key_ceremony_round3 ###</span>
<span id="cb3-35"><a href="#cb3-35" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-36"><a href="#cb3-36" aria-hidden="true" tabindex="-1"></a>docker exec election-guardian1-1 poetry run /scripts/guardian.py key-ceremony --public-dir /data/public --private-dir /data/private --ceremony-round 3 --guardian-id guardian_1 --guardian-sequence-order 1</span>
<span id="cb3-37"><a href="#cb3-37" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-38"><a href="#cb3-38" aria-hidden="true" tabindex="-1"></a>docker exec election-guardian2-1 poetry run /scripts/guardian.py key-ceremony --public-dir /data/public --private-dir /data/private --ceremony-round 3 --guardian-id guardian_2 --guardian-sequence-order 2</span>
<span id="cb3-39"><a href="#cb3-39" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-40"><a href="#cb3-40" aria-hidden="true" tabindex="-1"></a>docker exec election-guardian3-1 poetry run /scripts/guardian.py key-ceremony --public-dir /data/public --private-dir /data/private --ceremony-round 3 --guardian-id guardian_3 --guardian-sequence-order 3</span>
<span id="cb3-41"><a href="#cb3-41" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-42"><a href="#cb3-42" aria-hidden="true" tabindex="-1"></a>### publish_joint_key ###</span>
<span id="cb3-43"><a href="#cb3-43" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-44"><a href="#cb3-44" aria-hidden="true" tabindex="-1"></a>docker exec election-admin1-1 poetry run /scripts/admin.py publish-joint-key --public-dir /data/public</span>
<span id="cb3-45"><a href="#cb3-45" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-46"><a href="#cb3-46" aria-hidden="true" tabindex="-1"></a>### build_election ###</span>
<span id="cb3-47"><a href="#cb3-47" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-48"><a href="#cb3-48" aria-hidden="true" tabindex="-1"></a>docker exec election-admin1-1 poetry run /scripts/admin.py build-election --public-dir /data/public</span>
<span id="cb3-49"><a href="#cb3-49" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-50"><a href="#cb3-50" aria-hidden="true" tabindex="-1"></a>### add_devices ###</span>
<span id="cb3-51"><a href="#cb3-51" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-52"><a href="#cb3-52" aria-hidden="true" tabindex="-1"></a>docker exec election-device1-1 poetry run /scripts/device.py add-device --public-dir /data/public --device-number 1</span>
<span id="cb3-53"><a href="#cb3-53" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-54"><a href="#cb3-54" aria-hidden="true" tabindex="-1"></a>docker exec election-device2-1 poetry run /scripts/device.py add-device --public-dir /data/public --device-number 2</span>
<span id="cb3-55"><a href="#cb3-55" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-56"><a href="#cb3-56" aria-hidden="true" tabindex="-1"></a>docker exec election-device3-1 poetry run /scripts/device.py add-device --public-dir /data/public --device-number 3</span>
<span id="cb3-57"><a href="#cb3-57" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-58"><a href="#cb3-58" aria-hidden="true" tabindex="-1"></a>docker exec election-device4-1 poetry run /scripts/device.py add-device --public-dir /data/public --device-number 4</span>
<span id="cb3-59"><a href="#cb3-59" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-60"><a href="#cb3-60" aria-hidden="true" tabindex="-1"></a>### vote_all ###</span>
<span id="cb3-61"><a href="#cb3-61" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-62"><a href="#cb3-62" aria-hidden="true" tabindex="-1"></a>docker exec election-device1-1 poetry run /scripts/device.py vote --public-dir /data/public --private-dir /data/private --device-number 1 --candidate Yes --spoil True</span>
<span id="cb3-63"><a href="#cb3-63" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-64"><a href="#cb3-64" aria-hidden="true" tabindex="-1"></a>docker exec election-device2-1 poetry run /scripts/device.py vote --public-dir /data/public --private-dir /data/private --device-number 2 --candidate Yes --spoil True</span>
<span id="cb3-65"><a href="#cb3-65" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-66"><a href="#cb3-66" aria-hidden="true" tabindex="-1"></a>docker exec election-device3-1 poetry run /scripts/device.py vote --public-dir /data/public --private-dir /data/private --device-number 3 --candidate Yes --spoil True</span>
<span id="cb3-67"><a href="#cb3-67" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-68"><a href="#cb3-68" aria-hidden="true" tabindex="-1"></a>docker exec election-device4-1 poetry run /scripts/device.py vote --public-dir /data/public --private-dir /data/private --device-number 4 --candidate Yes --spoil False</span>
<span id="cb3-69"><a href="#cb3-69" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-70"><a href="#cb3-70" aria-hidden="true" tabindex="-1"></a>docker exec election-device1-1 poetry run /scripts/device.py vote --public-dir /data/public --private-dir /data/private --device-number 1 --candidate No --spoil True</span>
<span id="cb3-71"><a href="#cb3-71" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-72"><a href="#cb3-72" aria-hidden="true" tabindex="-1"></a>docker exec election-device2-1 poetry run /scripts/device.py vote --public-dir /data/public --private-dir /data/private --device-number 2 --candidate No --spoil True</span>
<span id="cb3-73"><a href="#cb3-73" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-74"><a href="#cb3-74" aria-hidden="true" tabindex="-1"></a>docker exec election-device3-1 poetry run /scripts/device.py vote --public-dir /data/public --private-dir /data/private --device-number 3 --candidate No --spoil False</span>
<span id="cb3-75"><a href="#cb3-75" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-76"><a href="#cb3-76" aria-hidden="true" tabindex="-1"></a>docker exec election-device4-1 poetry run /scripts/device.py vote --public-dir /data/public --private-dir /data/private --device-number 4 --candidate No --spoil False</span>
<span id="cb3-77"><a href="#cb3-77" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-78"><a href="#cb3-78" aria-hidden="true" tabindex="-1"></a>docker exec election-device1-1 poetry run /scripts/device.py vote --public-dir /data/public --private-dir /data/private --device-number 1 --candidate Unsure --spoil True</span>
<span id="cb3-79"><a href="#cb3-79" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-80"><a href="#cb3-80" aria-hidden="true" tabindex="-1"></a>docker exec election-device2-1 poetry run /scripts/device.py vote --public-dir /data/public --private-dir /data/private --device-number 2 --candidate Unsure --spoil False</span>
<span id="cb3-81"><a href="#cb3-81" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-82"><a href="#cb3-82" aria-hidden="true" tabindex="-1"></a>docker exec election-device3-1 poetry run /scripts/device.py vote --public-dir /data/public --private-dir /data/private --device-number 3 --candidate Unsure --spoil False</span>
<span id="cb3-83"><a href="#cb3-83" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-84"><a href="#cb3-84" aria-hidden="true" tabindex="-1"></a>docker exec election-device4-1 poetry run /scripts/device.py vote --public-dir /data/public --private-dir /data/private --device-number 4 --candidate Unsure --spoil False</span>
<span id="cb3-85"><a href="#cb3-85" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-86"><a href="#cb3-86" aria-hidden="true" tabindex="-1"></a>### tally ###</span>
<span id="cb3-87"><a href="#cb3-87" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-88"><a href="#cb3-88" aria-hidden="true" tabindex="-1"></a>docker exec election-admin1-1 poetry run /scripts/admin.py tally --public-dir /data/public</span>
<span id="cb3-89"><a href="#cb3-89" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-90"><a href="#cb3-90" aria-hidden="true" tabindex="-1"></a>### decrypt_shares ###</span>
<span id="cb3-91"><a href="#cb3-91" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-92"><a href="#cb3-92" aria-hidden="true" tabindex="-1"></a>docker exec election-guardian1-1 poetry run /scripts/guardian.py decrypt-shares --public-dir /data/public --private-dir /data/private --guardian-id guardian_1</span>
<span id="cb3-93"><a href="#cb3-93" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-94"><a href="#cb3-94" aria-hidden="true" tabindex="-1"></a>computed guardian_1 decryption share of election tally</span>
<span id="cb3-95"><a href="#cb3-95" aria-hidden="true" tabindex="-1"></a>computed guardian_1 decryption share of ballot-f512d9a0-e023-11f0-8c36-ce89c2e804da</span>
<span id="cb3-96"><a href="#cb3-96" aria-hidden="true" tabindex="-1"></a>computed guardian_1 decryption share of ballot-f38097b2-e023-11f0-bd15-faf5e3d58ded</span>
<span id="cb3-97"><a href="#cb3-97" aria-hidden="true" tabindex="-1"></a>computed guardian_1 decryption share of ballot-f595f4d4-e023-11f0-aea1-faf5e3d58ded</span>
<span id="cb3-98"><a href="#cb3-98" aria-hidden="true" tabindex="-1"></a>computed guardian_1 decryption share of ballot-f2f71be0-e023-11f0-8779-ce89c2e804da</span>
<span id="cb3-99"><a href="#cb3-99" aria-hidden="true" tabindex="-1"></a>computed guardian_1 decryption share of ballot-f408e6e4-e023-11f0-ba1e-2eb40f2ca782</span>
<span id="cb3-100"><a href="#cb3-100" aria-hidden="true" tabindex="-1"></a>computed guardian_1 decryption share of ballot-f71c771a-e023-11f0-8c68-ce89c2e804da</span>
<span id="cb3-101"><a href="#cb3-101" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-102"><a href="#cb3-102" aria-hidden="true" tabindex="-1"></a>docker exec election-guardian2-1 poetry run /scripts/guardian.py decrypt-shares --public-dir /data/public --private-dir /data/private --guardian-id guardian_2</span>
<span id="cb3-103"><a href="#cb3-103" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-104"><a href="#cb3-104" aria-hidden="true" tabindex="-1"></a>computed guardian_2 decryption share of election tally</span>
<span id="cb3-105"><a href="#cb3-105" aria-hidden="true" tabindex="-1"></a>computed guardian_2 decryption share of ballot-f512d9a0-e023-11f0-8c36-ce89c2e804da</span>
<span id="cb3-106"><a href="#cb3-106" aria-hidden="true" tabindex="-1"></a>computed guardian_2 decryption share of ballot-f38097b2-e023-11f0-bd15-faf5e3d58ded</span>
<span id="cb3-107"><a href="#cb3-107" aria-hidden="true" tabindex="-1"></a>computed guardian_2 decryption share of ballot-f595f4d4-e023-11f0-aea1-faf5e3d58ded</span>
<span id="cb3-108"><a href="#cb3-108" aria-hidden="true" tabindex="-1"></a>computed guardian_2 decryption share of ballot-f2f71be0-e023-11f0-8779-ce89c2e804da</span>
<span id="cb3-109"><a href="#cb3-109" aria-hidden="true" tabindex="-1"></a>computed guardian_2 decryption share of ballot-f408e6e4-e023-11f0-ba1e-2eb40f2ca782</span>
<span id="cb3-110"><a href="#cb3-110" aria-hidden="true" tabindex="-1"></a>computed guardian_2 decryption share of ballot-f71c771a-e023-11f0-8c68-ce89c2e804da</span>
<span id="cb3-111"><a href="#cb3-111" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-112"><a href="#cb3-112" aria-hidden="true" tabindex="-1"></a>docker exec election-guardian3-1 poetry run /scripts/guardian.py decrypt-shares --public-dir /data/public --private-dir /data/private --guardian-id guardian_3</span>
<span id="cb3-113"><a href="#cb3-113" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-114"><a href="#cb3-114" aria-hidden="true" tabindex="-1"></a>computed guardian_3 decryption share of election tally</span>
<span id="cb3-115"><a href="#cb3-115" aria-hidden="true" tabindex="-1"></a>computed guardian_3 decryption share of ballot-f512d9a0-e023-11f0-8c36-ce89c2e804da</span>
<span id="cb3-116"><a href="#cb3-116" aria-hidden="true" tabindex="-1"></a>computed guardian_3 decryption share of ballot-f38097b2-e023-11f0-bd15-faf5e3d58ded</span>
<span id="cb3-117"><a href="#cb3-117" aria-hidden="true" tabindex="-1"></a>computed guardian_3 decryption share of ballot-f595f4d4-e023-11f0-aea1-faf5e3d58ded</span>
<span id="cb3-118"><a href="#cb3-118" aria-hidden="true" tabindex="-1"></a>computed guardian_3 decryption share of ballot-f2f71be0-e023-11f0-8779-ce89c2e804da</span>
<span id="cb3-119"><a href="#cb3-119" aria-hidden="true" tabindex="-1"></a>computed guardian_3 decryption share of ballot-f408e6e4-e023-11f0-ba1e-2eb40f2ca782</span>
<span id="cb3-120"><a href="#cb3-120" aria-hidden="true" tabindex="-1"></a>computed guardian_3 decryption share of ballot-f71c771a-e023-11f0-8c68-ce89c2e804da</span>
<span id="cb3-121"><a href="#cb3-121" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-122"><a href="#cb3-122" aria-hidden="true" tabindex="-1"></a>### decrypt_results ###</span>
<span id="cb3-123"><a href="#cb3-123" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-124"><a href="#cb3-124" aria-hidden="true" tabindex="-1"></a>docker exec election-admin1-1 poetry run /scripts/admin.py decrypt-results --public-dir /data/public</span>
<span id="cb3-125"><a href="#cb3-125" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-126"><a href="#cb3-126" aria-hidden="true" tabindex="-1"></a>decrypted tally</span>
<span id="cb3-127"><a href="#cb3-127" aria-hidden="true" tabindex="-1"></a>decrypted ballot-f512d9a0-e023-11f0-8c36-ce89c2e804da</span>
<span id="cb3-128"><a href="#cb3-128" aria-hidden="true" tabindex="-1"></a>decrypted ballot-f38097b2-e023-11f0-bd15-faf5e3d58ded</span>
<span id="cb3-129"><a href="#cb3-129" aria-hidden="true" tabindex="-1"></a>decrypted ballot-f595f4d4-e023-11f0-aea1-faf5e3d58ded</span>
<span id="cb3-130"><a href="#cb3-130" aria-hidden="true" tabindex="-1"></a>decrypted ballot-f2f71be0-e023-11f0-8779-ce89c2e804da</span>
<span id="cb3-131"><a href="#cb3-131" aria-hidden="true" tabindex="-1"></a>decrypted ballot-f408e6e4-e023-11f0-ba1e-2eb40f2ca782</span>
<span id="cb3-132"><a href="#cb3-132" aria-hidden="true" tabindex="-1"></a>decrypted ballot-f71c771a-e023-11f0-8c68-ce89c2e804da</span>
<span id="cb3-133"><a href="#cb3-133" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-134"><a href="#cb3-134" aria-hidden="true" tabindex="-1"></a>### summary ###</span>
<span id="cb3-135"><a href="#cb3-135" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-136"><a href="#cb3-136" aria-hidden="true" tabindex="-1"></a>docker exec election-admin1-1 poetry run /scripts/admin.py summary --public-dir /data/public</span>
<span id="cb3-137"><a href="#cb3-137" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-138"><a href="#cb3-138" aria-hidden="true" tabindex="-1"></a>----------------------------------------</span>
<span id="cb3-139"><a href="#cb3-139" aria-hidden="true" tabindex="-1"></a>Individual spoiled ballots</span>
<span id="cb3-140"><a href="#cb3-140" aria-hidden="true" tabindex="-1"></a>----------------------------------------</span>
<span id="cb3-141"><a href="#cb3-141" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-142"><a href="#cb3-142" aria-hidden="true" tabindex="-1"></a>f512d9a0-e023-11f0-8c36-ce89c2e804da</span>
<span id="cb3-143"><a href="#cb3-143" aria-hidden="true" tabindex="-1"></a>  Should pineapple be banned on pizza? No</span>
<span id="cb3-144"><a href="#cb3-144" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-145"><a href="#cb3-145" aria-hidden="true" tabindex="-1"></a>f38097b2-e023-11f0-bd15-faf5e3d58ded</span>
<span id="cb3-146"><a href="#cb3-146" aria-hidden="true" tabindex="-1"></a>  Should pineapple be banned on pizza? Yes</span>
<span id="cb3-147"><a href="#cb3-147" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-148"><a href="#cb3-148" aria-hidden="true" tabindex="-1"></a>f595f4d4-e023-11f0-aea1-faf5e3d58ded</span>
<span id="cb3-149"><a href="#cb3-149" aria-hidden="true" tabindex="-1"></a>  Should pineapple be banned on pizza? No</span>
<span id="cb3-150"><a href="#cb3-150" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-151"><a href="#cb3-151" aria-hidden="true" tabindex="-1"></a>f2f71be0-e023-11f0-8779-ce89c2e804da</span>
<span id="cb3-152"><a href="#cb3-152" aria-hidden="true" tabindex="-1"></a>  Should pineapple be banned on pizza? Yes</span>
<span id="cb3-153"><a href="#cb3-153" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-154"><a href="#cb3-154" aria-hidden="true" tabindex="-1"></a>f408e6e4-e023-11f0-ba1e-2eb40f2ca782</span>
<span id="cb3-155"><a href="#cb3-155" aria-hidden="true" tabindex="-1"></a>  Should pineapple be banned on pizza? Yes</span>
<span id="cb3-156"><a href="#cb3-156" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-157"><a href="#cb3-157" aria-hidden="true" tabindex="-1"></a>f71c771a-e023-11f0-8c68-ce89c2e804da</span>
<span id="cb3-158"><a href="#cb3-158" aria-hidden="true" tabindex="-1"></a>  Should pineapple be banned on pizza? Unsure</span>
<span id="cb3-159"><a href="#cb3-159" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-160"><a href="#cb3-160" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-161"><a href="#cb3-161" aria-hidden="true" tabindex="-1"></a>----------------------------------------</span>
<span id="cb3-162"><a href="#cb3-162" aria-hidden="true" tabindex="-1"></a>Tally of all cast ballots</span>
<span id="cb3-163"><a href="#cb3-163" aria-hidden="true" tabindex="-1"></a>----------------------------------------</span>
<span id="cb3-164"><a href="#cb3-164" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-165"><a href="#cb3-165" aria-hidden="true" tabindex="-1"></a>Should pineapple be banned on pizza?</span>
<span id="cb3-166"><a href="#cb3-166" aria-hidden="true" tabindex="-1"></a>  Unsure: 3</span>
<span id="cb3-167"><a href="#cb3-167" aria-hidden="true" tabindex="-1"></a>  No: 2</span>
<span id="cb3-168"><a href="#cb3-168" aria-hidden="true" tabindex="-1"></a>  Yes: 1</span>
<span id="cb3-169"><a href="#cb3-169" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-170"><a href="#cb3-170" aria-hidden="true" tabindex="-1"></a>### teardown ###</span>
<span id="cb3-171"><a href="#cb3-171" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-172"><a href="#cb3-172" aria-hidden="true" tabindex="-1"></a> Container election-device3-1  Stopping</span>
<span id="cb3-173"><a href="#cb3-173" aria-hidden="true" tabindex="-1"></a> Container election-guardian3-1  Stopping</span>
<span id="cb3-174"><a href="#cb3-174" aria-hidden="true" tabindex="-1"></a> ...</span>
<span id="cb3-175"><a href="#cb3-175" aria-hidden="true" tabindex="-1"></a> Container election-guardian2-1  Removed</span>
<span id="cb3-176"><a href="#cb3-176" aria-hidden="true" tabindex="-1"></a> Network election  Removing</span>
<span id="cb3-177"><a href="#cb3-177" aria-hidden="true" tabindex="-1"></a> Network election  Removed</span></code></pre></div>
<p>We’ll go into detail about what’s going on at each stage in the protocol in future updates.
For now, just try editing <a href="https://github.com/jefdaj/electionguard-cardano/tree/trunk/milestone1/election/election.json">election.json</a> and re-running it to convince yourself
that the vote totals in <code>data/public/3_results/3_summary.json</code> always match.
They should look something like this.</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode json"><code class="sourceCode json"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="fu">{</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>  <span class="dt">&quot;Tally of all cast ballots&quot;</span><span class="fu">:</span> <span class="ot">[</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>    <span class="fu">{</span></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a>      <span class="dt">&quot;question&quot;</span><span class="fu">:</span> <span class="st">&quot;Should pineapple be banned on pizza?&quot;</span><span class="fu">,</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>      <span class="dt">&quot;votes&quot;</span><span class="fu">:</span> <span class="fu">{</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;Unsure&quot;</span><span class="fu">:</span> <span class="dv">3</span><span class="fu">,</span></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;No&quot;</span><span class="fu">:</span> <span class="dv">2</span><span class="fu">,</span></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a>        <span class="dt">&quot;Yes&quot;</span><span class="fu">:</span> <span class="dv">1</span></span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a>      <span class="fu">}</span></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a>    <span class="fu">}</span></span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a>  <span class="ot">]</span><span class="fu">,</span></span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a>  <span class="er">...</span></span>
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true" tabindex="-1"></a><span class="fu">}</span></span></code></pre></div>
<p>In <a href="/posts/2025/12/23/egc-dev02-election-verifier">dev update #2</a> I’ll show how independent observers can verify the public files.</p>
<h1 id="dev-options">Dev options</h1>
<p>When hacking on <code>election.py</code>, it might be helpful to run one step in the election protocol at a time:</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="ex">nix</span> develop</span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="ex">./election.sh</span> <span class="at">--single-step</span> setup</span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="ex">./election.sh</span> <span class="at">--single-step</span> build_manifest</span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="ex">./election.sh</span> <span class="at">--single-step</span> ...</span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="ex">./election.sh</span> <span class="at">--single-step</span> teardown</span></code></pre></div>
<p>There’s also a <code>--pause-to-explain</code> option, which I wrote for the video but also use to inspect the generated files as it runs.</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode bash"><code class="sourceCode bash"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="ex">nix</span> develop</span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a><span class="ex">./election.sh</span> <span class="at">--pause-to-explain</span></span></code></pre></div>]]></summary>
</entry>
<entry>
    <title>Crypto Taxes the Hard Way: Transfer Accounts</title>
    <link href="https://cryptoisland.blog/posts/2025/11/24/crypto-taxes-the-hard-way-transfer-accounts" />
    <id>https://cryptoisland.blog/posts/2025/11/24/crypto-taxes-the-hard-way-transfer-accounts</id>
    <published>2025-11-24T00:00:00Z</published>
    <updated>2025-11-24T00:00:00Z</updated>
    <summary type="html"><![CDATA[
<div class="toc"><div class="header">Contents</div>
<ul>
<li><a href="#the-easy-way" id="toc-the-easy-way">The easy way</a></li>
<li><a href="#the-elegant-sustainable-way" id="toc-the-elegant-sustainable-way">The elegant, sustainable way</a></li>
<li><a href="#handling-many-accounts" id="toc-handling-many-accounts">Handling many accounts</a></li>
<li><a href="#handling-many-currencies" id="toc-handling-many-currencies">Handling many currencies</a></li>
</ul>
<center class="reminder"><img src="transfers.png"></img></center></div>
<p><small>
<em>Disclaimer: nothing on this blog is advice about the substance of your taxes.</em> I have no background in accounting and no idea whether this code will produce valid results in your (or any!) tax situation.
</small></p>
<!-- TODO quick check that there isn't already a good guide on this stuff! if so just link to it -->
<p>Transfer accounts are relatively standard accounting trick that I took a while to figure out.
You should learn them straight away though! It’ll save a lot of time when using
<a href="https://hledger.org/">Hledger</a> or plain text accounting generally, and they’re especially useful for <a href="/posts/2023/02/18/crypto-taxes-the-hard-way">crypto taxes</a>.</p>
<h1 id="the-easy-way">The easy way</h1>
<p>Suppose you have accounts with BigBank and MockEx, along with a Bitcoin wallet, and you’re in the USA.
Here’s a diagram of the accounts, transfers, and currencies you probably need to track:</p>
<p><img src="normal.svg"></img></p>
<p>The easiest way to represent it in hledger is probably to parse all the transfers from the MockEx data
and ignore them in the BigBank + Wallet data, like this:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode txt"><code class="sourceCode default"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a># mockex.rules</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a># One account is always the main one for the current file</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a>account1 assets:mockex</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a># The other account and currency can be decided by regex matches</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a>if (Incoming|Outgoing).*USD</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>  account2 assets:bigbank</span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>  currency USD</span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a>if (Withdrawal|Deposit).*BTC</span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>  account2 assets:wallet</span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a>  currency BTC</span></code></pre></div>
<div class="sourceCode" id="cb2"><pre class="sourceCode txt"><code class="sourceCode default"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a># bigbank.rules</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a># Regex to match the bank&#39;s descriptions</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>if TRANSFER (TO|FROM) MOCKEX</span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>  skip</span></code></pre></div>
<div class="sourceCode" id="cb3"><pre class="sourceCode txt"><code class="sourceCode default"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a># wallet.rules</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a># Regex to match your own annotations</span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a># (Alternatively, can use individual TXIDs)</span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>if mockex</span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a>  skip</span></code></pre></div>
<!--
Ignoring them sounds like it would be a problem, but you can still check that all the accounts add up in hledger:
-->
<!-- TODO example -->
<p>If this is your only way of trading crypto, you might even be able to finish
your taxes without parsing the bank or wallet data at all!</p>
<h1 id="the-elegant-sustainable-way">The elegant, sustainable way</h1>
<p>Sadly, the easy way doesn’t scale.
As you add accounts it’ll become harder to remember which transfers should be parsed and which should be ignored.
At some point, you should really add transfer accounts:</p>
<p><img src="implicit.svg"></img></p>
<p>Each transfer will now be recorded as two halves: into the transfer account, and back out (not necessarily in that order).
For the cost of adding a few extra hledger accounts,
we get a graph that can be neatly decomposed to match the data files.</p>
<p><img src="implicit-split.svg"></img></p>
<p>Now we can parse all the transfers from each source and dump them into the main journal,
and they should all balance.</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode txt"><code class="sourceCode default"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a># bigbank.rules</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>currency USD</span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>account1 assets:bigbank</span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a>if TRANSFER (TO|FROM) MOCKEX</span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>  account2 transfers:USD</span></code></pre></div>
<div class="sourceCode" id="cb5"><pre class="sourceCode txt"><code class="sourceCode default"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a># mockex.rules</span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a>account1 assets:mockex</span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a>if (Incoming|Outgoing).*USD</span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a>  account2 transfers:USD</span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a>  currency USD</span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a>if (Withdrawal|Deposit).*BTC</span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a>  account2 transfers:BTC</span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a>  currency BTC</span></code></pre></div>
<div class="sourceCode" id="cb6"><pre class="sourceCode txt"><code class="sourceCode default"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a># wallet.rules</span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a>currency BTC</span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a>account1 assets:wallet</span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a>if mockex</span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a>  account2 transfers:BTC</span></code></pre></div>
<p>This isn’t only an ergonomic improvement.
It also makes it much easier to double check that the amounts reported by each institution match up.
(You might be surprised—or not—to learn that several large US exchanges do weird rounding errors in their own favor.)
By checking that the transfer accounts zero out periodically, we can catch various errors.</p>
<p>Speaking of which, it would also be reasonable to call these “error” accounts rather than “transfers”,
because their accumulated balances track imbalances in your accounting.
That might be more appealing if you already have an <code>error</code> account for other reasons.</p>
<!-- TODO example? -->
<!--
It can be annoying to have to account for all the edge cases,
but IMO the confidence of knowing everything balances is worth the hassle.
-->
<h1 id="handling-many-accounts">Handling many accounts</h1>
<p>What if you have more than one bank account, or more than one wallet?
<b>This will handle it automatically with no additional work!</b></p>
<!-- TODO move this part to the tradfi post: -->
<p>It also handles multiple accounts within the same bank.
For example if you have checking, credit, and savings/money market accounts you can just
regex match on whatever description the accounts use for incoming and outgoing transfers,
and call them all <code>transfers:USD</code>.</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode txt"><code class="sourceCode default"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a># bigbank-checking.rules</span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a>currency USD</span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a># Other accounts will be the same except this line</span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a>account1 assets:bigbank:checking</span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a># ... unless they also need different regexes</span>
<span id="cb7-9"><a href="#cb7-9" aria-hidden="true" tabindex="-1"></a>if ^TRANSFER.*</span>
<span id="cb7-10"><a href="#cb7-10" aria-hidden="true" tabindex="-1"></a>  account2 transfers:USD</span>
<span id="cb7-11"><a href="#cb7-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-12"><a href="#cb7-12" aria-hidden="true" tabindex="-1"></a># (The credit accounts might also have their + and -</span>
<span id="cb7-13"><a href="#cb7-13" aria-hidden="true" tabindex="-1"></a># signs flipped, but that&#39;s out of scope for today.)</span></code></pre></div>
<!-- TODO example -->
<h1 id="handling-many-currencies">Handling many currencies</h1>
<p>Only a little bit of additional work this time. Suppose you have an Ethereum
wallet, and it transacts in multiple currencies: moving ERC20 tokens, paying
fees in ETH, getting airdrops or spam, etc. Technically you could send them all
via the same <code>transfers:ETH</code> account, but I find it easier to make one per
token. That way I don’t have to remember that <code>transfers:ETH</code> can contain
other currencies, which used to trip me up a lot. Either way is fine though.</p>
<p>Here’s what a transfer might look like in this style after parsing a csv from
each of your wallets and dumping them both into the main journal:</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode txt"><code class="sourceCode default"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a>2025-11-01 wallet1 send LINK</span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a>  assets:wallet1  -10.000 LINK</span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a>  assets:wallet1   -0.001 ETH</span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a>  expenses:fees     0.001 ETH</span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a>  transfers:LINK   10.000 LINK</span>
<span id="cb8-6"><a href="#cb8-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-7"><a href="#cb8-7" aria-hidden="true" tabindex="-1"></a>2025-11-01 wallet2 receive LINK</span>
<span id="cb8-8"><a href="#cb8-8" aria-hidden="true" tabindex="-1"></a>  assets:wallet2   10.000 LINK</span>
<span id="cb8-9"><a href="#cb8-9" aria-hidden="true" tabindex="-1"></a>  transfers:LINK  -10.000 LINK</span></code></pre></div>
<!-- TODO find one of your transfers with actual gas price -->
<p>The last pro tip for today is that if you name your transfer accounts based on
tickers (<code>LINK</code> rather than <code>chainlink</code>), you can generate them as needed in
your rules files.</p>]]></summary>
</entry>
<entry>
    <title>Introducing BigTrees!</title>
    <link href="https://cryptoisland.blog/posts/2025/07/16/introducing-bigtrees" />
    <id>https://cryptoisland.blog/posts/2025/07/16/introducing-bigtrees</id>
    <published>2025-07-16T00:00:00Z</published>
    <updated>2025-10-27</updated>
    <summary type="html"><![CDATA[
<div class="toc"><div class="header">Contents</div>
<ul>
<li><a href="#test-data" id="toc-test-data">Test data</a></li>
<li><a href="#minimal-dedup-command" id="toc-minimal-dedup-command">Minimal dedup command</a>
<ul>
<li><a href="#using-a-.bigtree-file" id="toc-using-a-.bigtree-file">Using a <code>.bigtree</code> file</a></li>
</ul></li>
<li><a href="#minimal-diff-command" id="toc-minimal-diff-command">Minimal diff command</a></li>
<li><a href="#minimal-find-command" id="toc-minimal-find-command">Minimal find command</a></li>
</ul>
<center class="reminder"><img src="bigtrees.png"></img></center></div>
<p>I’ve been working on <a href="https://jefdaj.github.io/bigtrees">a Haskell program</a> to dedup large collections
of files efficiently. It’s still under heavy development, but I think it’s at
the point now where it could be useful for a few intrepid testers/power users.</p>
<p><em>Most people should default to using an established tool like
<a href="https://github.com/adrianlopezroche/fdupes"><code>fdupes</code></a> for now.</em> Then again, if you’re reading this you may be an
exception… consider trying <a href="https://jefdaj.github.io/bigtrees"><code>bigtrees</code></a> if you like/need some of
the features I’m working on, or have an idea about how it could be made to fit
your workflow better than the established tools!</p>
<p>I’ll go into more detail on all the cool things you can do with each of these
commands and others “soon”; for now here are 3 short examples.</p>
<h1 id="test-data">Test data</h1>
<p>You can already try <code>bigtrees</code> on your own data, of course! But maybe you’re a
little more careful than that? If so, use <a href="./fetch-test-files.py">this Python script</a>. It’ll
download the <a href="https://github.com/torvalds/linux">Linux kernel source code</a> and <a href="https://github.com/Josef-Friedrich/test-files">a nice git repo with some
example pictures, music etc</a>. Then it’ll duplicate them a few times
to get ~1 million test files taking up 18G total.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode .bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> export TMPDIR=/tmp</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> time ./fetch-test-files.py</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="ex">downloading</span> <span class="st">&#39;/tmp/test-files/josef-friedrichj.zip&#39;</span>... ok</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="ex">unzipping</span> <span class="st">&#39;/tmp/test-files/josef-friedrichj.zip&#39;</span>... ok</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="ex">copying</span> <span class="st">&#39;/tmp/test-files/josef-friedrichj&#39;</span> <span class="at">-</span><span class="op">&gt;</span> <span class="st">&#39;/tmp/test-files/josef-friedrichj-dupe1&#39;</span>... ok</span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="ex">copying</span> <span class="st">&#39;/tmp/test-files/josef-friedrichj&#39;</span> <span class="at">-</span><span class="op">&gt;</span> <span class="st">&#39;/tmp/test-files/josef-friedrichj-dupe2&#39;</span>... ok</span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="ex">copying</span> <span class="st">&#39;/tmp/test-files/josef-friedrichj&#39;</span> <span class="at">-</span><span class="op">&gt;</span> <span class="st">&#39;/tmp/test-files/josef-friedrichj-dupe3&#39;</span>... ok</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a><span class="ex">copying</span> <span class="st">&#39;/tmp/test-files/josef-friedrichj&#39;</span> <span class="at">-</span><span class="op">&gt;</span> <span class="st">&#39;/tmp/test-files/josef-friedrichj-dupe4&#39;</span>... ok</span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a><span class="ex">copying</span> <span class="st">&#39;/tmp/test-files/josef-friedrichj&#39;</span> <span class="at">-</span><span class="op">&gt;</span> <span class="st">&#39;/tmp/test-files/josef-friedrichj-dupe5&#39;</span>... ok</span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a><span class="ex">copying</span> <span class="st">&#39;/tmp/test-files/josef-friedrichj&#39;</span> <span class="at">-</span><span class="op">&gt;</span> <span class="st">&#39;/tmp/test-files/josef-friedrichj-dupe6&#39;</span>... ok</span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a><span class="ex">copying</span> <span class="st">&#39;/tmp/test-files/josef-friedrichj&#39;</span> <span class="at">-</span><span class="op">&gt;</span> <span class="st">&#39;/tmp/test-files/josef-friedrichj-dupe7&#39;</span>... ok</span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a><span class="ex">copying</span> <span class="st">&#39;/tmp/test-files/josef-friedrichj&#39;</span> <span class="at">-</span><span class="op">&gt;</span> <span class="st">&#39;/tmp/test-files/josef-friedrichj-dupe8&#39;</span>... ok</span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a><span class="ex">copying</span> <span class="st">&#39;/tmp/test-files/josef-friedrichj&#39;</span> <span class="at">-</span><span class="op">&gt;</span> <span class="st">&#39;/tmp/test-files/josef-friedrichj-dupe9&#39;</span>... ok</span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a><span class="ex">downloading</span> <span class="st">&#39;/tmp/test-files/linux-source-code.zip&#39;</span>... ok</span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a><span class="ex">unzipping</span> <span class="st">&#39;/tmp/test-files/linux-source-code.zip&#39;</span>... ok</span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a><span class="ex">copying</span> <span class="st">&#39;/tmp/test-files/linux-source-code&#39;</span> <span class="at">-</span><span class="op">&gt;</span> <span class="st">&#39;/tmp/test-files/linux-source-code-dupe1&#39;</span>... ok</span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a><span class="ex">copying</span> <span class="st">&#39;/tmp/test-files/linux-source-code&#39;</span> <span class="at">-</span><span class="op">&gt;</span> <span class="st">&#39;/tmp/test-files/linux-source-code-dupe2&#39;</span>... ok</span>
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true" tabindex="-1"></a><span class="ex">copying</span> <span class="st">&#39;/tmp/test-files/linux-source-code&#39;</span> <span class="at">-</span><span class="op">&gt;</span> <span class="st">&#39;/tmp/test-files/linux-source-code-dupe3&#39;</span>... ok</span>
<span id="cb1-19"><a href="#cb1-19" aria-hidden="true" tabindex="-1"></a><span class="ex">copying</span> <span class="st">&#39;/tmp/test-files/linux-source-code&#39;</span> <span class="at">-</span><span class="op">&gt;</span> <span class="st">&#39;/tmp/test-files/linux-source-code-dupe4&#39;</span>... ok</span>
<span id="cb1-20"><a href="#cb1-20" aria-hidden="true" tabindex="-1"></a><span class="ex">copying</span> <span class="st">&#39;/tmp/test-files/linux-source-code&#39;</span> <span class="at">-</span><span class="op">&gt;</span> <span class="st">&#39;/tmp/test-files/linux-source-code-dupe5&#39;</span>... ok</span>
<span id="cb1-21"><a href="#cb1-21" aria-hidden="true" tabindex="-1"></a><span class="ex">copying</span> <span class="st">&#39;/tmp/test-files/linux-source-code&#39;</span> <span class="at">-</span><span class="op">&gt;</span> <span class="st">&#39;/tmp/test-files/linux-source-code-dupe6&#39;</span>... ok</span>
<span id="cb1-22"><a href="#cb1-22" aria-hidden="true" tabindex="-1"></a><span class="ex">copying</span> <span class="st">&#39;/tmp/test-files/linux-source-code&#39;</span> <span class="at">-</span><span class="op">&gt;</span> <span class="st">&#39;/tmp/test-files/linux-source-code-dupe7&#39;</span>... ok</span>
<span id="cb1-23"><a href="#cb1-23" aria-hidden="true" tabindex="-1"></a><span class="ex">copying</span> <span class="st">&#39;/tmp/test-files/linux-source-code&#39;</span> <span class="at">-</span><span class="op">&gt;</span> <span class="st">&#39;/tmp/test-files/linux-source-code-dupe8&#39;</span>... ok</span>
<span id="cb1-24"><a href="#cb1-24" aria-hidden="true" tabindex="-1"></a><span class="ex">copying</span> <span class="st">&#39;/tmp/test-files/linux-source-code&#39;</span> <span class="at">-</span><span class="op">&gt;</span> <span class="st">&#39;/tmp/test-files/linux-source-code-dupe9&#39;</span>... ok</span>
<span id="cb1-25"><a href="#cb1-25" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-26"><a href="#cb1-26" aria-hidden="true" tabindex="-1"></a><span class="ex">real</span>    3m11.745s</span>
<span id="cb1-27"><a href="#cb1-27" aria-hidden="true" tabindex="-1"></a><span class="ex">user</span>    0m23.659s</span>
<span id="cb1-28"><a href="#cb1-28" aria-hidden="true" tabindex="-1"></a><span class="ex">sys</span>     0m22.355s</span></code></pre></div>
<div class="sourceCode" id="cb2"><pre class="sourceCode .bash"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> find test-files <span class="kw">|</span> <span class="fu">wc</span> <span class="at">-l</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="ex">957363</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> du <span class="at">-h</span> test-files <span class="kw">|</span> <span class="fu">tail</span> <span class="at">-n1</span></span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a><span class="ex">18G</span>     test-files</span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> tree <span class="at">-L</span> 1 test-files</span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files</span></span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> josef-friedrichj</span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> josef-friedrichj-dupe1</span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> josef-friedrichj-dupe2</span>
<span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> josef-friedrichj-dupe3</span>
<span id="cb2-13"><a href="#cb2-13" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> josef-friedrichj-dupe4</span>
<span id="cb2-14"><a href="#cb2-14" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> josef-friedrichj-dupe5</span>
<span id="cb2-15"><a href="#cb2-15" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> josef-friedrichj-dupe6</span>
<span id="cb2-16"><a href="#cb2-16" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> josef-friedrichj-dupe7</span>
<span id="cb2-17"><a href="#cb2-17" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> josef-friedrichj-dupe8</span>
<span id="cb2-18"><a href="#cb2-18" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> josef-friedrichj-dupe9</span>
<span id="cb2-19"><a href="#cb2-19" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> josef-friedrichj.zip</span>
<span id="cb2-20"><a href="#cb2-20" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> linux-source-code</span>
<span id="cb2-21"><a href="#cb2-21" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> linux-source-code-dupe1</span>
<span id="cb2-22"><a href="#cb2-22" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> linux-source-code-dupe2</span>
<span id="cb2-23"><a href="#cb2-23" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> linux-source-code-dupe3</span>
<span id="cb2-24"><a href="#cb2-24" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> linux-source-code-dupe4</span>
<span id="cb2-25"><a href="#cb2-25" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> linux-source-code-dupe5</span>
<span id="cb2-26"><a href="#cb2-26" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> linux-source-code-dupe6</span>
<span id="cb2-27"><a href="#cb2-27" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> linux-source-code-dupe7</span>
<span id="cb2-28"><a href="#cb2-28" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> linux-source-code-dupe8</span>
<span id="cb2-29"><a href="#cb2-29" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> linux-source-code-dupe9</span>
<span id="cb2-30"><a href="#cb2-30" aria-hidden="true" tabindex="-1"></a><span class="ex">└──</span> linux-source-code.zip</span>
<span id="cb2-31"><a href="#cb2-31" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-32"><a href="#cb2-32" aria-hidden="true" tabindex="-1"></a><span class="ex">21</span> directories, 2 files</span></code></pre></div>
<h1 id="minimal-dedup-command">Minimal dedup command</h1>
<p>I’m quite pleased with <em>how simple</em> this looks!
It took a lot of work to get it that way.</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode .bash"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> time bigtrees dupes test-files</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="co"># This is the default &#39;suggestions&#39; output format.</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="co"># It just suggests what you might delete manually yourself.</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="co"># You could save 861228 inodes by deleting all but one of these 10 duplicate directories</span></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/linux-source-code</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/linux-source-code-dupe1</span></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/linux-source-code-dupe2</span></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/linux-source-code-dupe3</span></span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/linux-source-code-dupe4</span></span>
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/linux-source-code-dupe5</span></span>
<span id="cb3-13"><a href="#cb3-13" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/linux-source-code-dupe6</span></span>
<span id="cb3-14"><a href="#cb3-14" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/linux-source-code-dupe7</span></span>
<span id="cb3-15"><a href="#cb3-15" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/linux-source-code-dupe8</span></span>
<span id="cb3-16"><a href="#cb3-16" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/linux-source-code-dupe9</span></span>
<span id="cb3-17"><a href="#cb3-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-18"><a href="#cb3-18" aria-hidden="true" tabindex="-1"></a><span class="co"># You could save 414 inodes by deleting all but one of these 10 duplicate directories</span></span>
<span id="cb3-19"><a href="#cb3-19" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/josef-friedrichj</span></span>
<span id="cb3-20"><a href="#cb3-20" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/josef-friedrichj-dupe1</span></span>
<span id="cb3-21"><a href="#cb3-21" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/josef-friedrichj-dupe2</span></span>
<span id="cb3-22"><a href="#cb3-22" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/josef-friedrichj-dupe3</span></span>
<span id="cb3-23"><a href="#cb3-23" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/josef-friedrichj-dupe4</span></span>
<span id="cb3-24"><a href="#cb3-24" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/josef-friedrichj-dupe5</span></span>
<span id="cb3-25"><a href="#cb3-25" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/josef-friedrichj-dupe6</span></span>
<span id="cb3-26"><a href="#cb3-26" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/josef-friedrichj-dupe7</span></span>
<span id="cb3-27"><a href="#cb3-27" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/josef-friedrichj-dupe8</span></span>
<span id="cb3-28"><a href="#cb3-28" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/josef-friedrichj-dupe9</span></span>
<span id="cb3-29"><a href="#cb3-29" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-30"><a href="#cb3-30" aria-hidden="true" tabindex="-1"></a><span class="ex">real</span>    2m42.149s</span>
<span id="cb3-31"><a href="#cb3-31" aria-hidden="true" tabindex="-1"></a><span class="ex">user</span>    3m29.705s</span>
<span id="cb3-32"><a href="#cb3-32" aria-hidden="true" tabindex="-1"></a><span class="ex">sys</span>     0m53.310s</span></code></pre></div>
<p>(I’ll go into a few cases where it’s not so perfect in future posts)</p>
<h2 id="using-a-.bigtree-file">Using a <code>.bigtree</code> file</h2>
<p>These save the directory structure as well as the hash of each file and folder.
They’re used when you want to hash files once and use the results
multiple times. The command above could equivalently be written like so:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode .bash"><code class="sourceCode bash"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> bigtrees hash test-files <span class="at">--output</span> test-files.bigtree</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> bigtrees dupes test-files.bigtree</span></code></pre></div>
<h1 id="minimal-diff-command">Minimal diff command</h1>
<p>This is meant to take an old and a new collection of files. You might use it to
compare a backup to your current documents, or an older backup to a newer one.
You can also mix and match actual files/dirs with saved <code>.bigtree</code> files. Let’s
edit <code>test-files</code> a little, and see if it can detect the changes.</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode .bash"><code class="sourceCode bash"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> rm <span class="at">-r</span> test-files/linux-source-code-dupe7/linux-master/</span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> rm <span class="at">-r</span> test-files/linux-source-code-dupe8/linux-master/drivers/pinctrl/realtek/</span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> echo <span class="st">&quot;a new file!&quot;</span> <span class="op">&gt;</span> test-files/linux-source-code-dupe8/linux-master/extra.txt</span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> time bigtrees diff test-files.bigtree test-files</span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a><span class="ex">removed</span> <span class="st">&#39;linux-source-code-dupe7/linux-master&#39;</span></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a><span class="ex">added</span> <span class="st">&#39;linux-source-code-dupe8/linux-master/extra.txt&#39;</span></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a><span class="ex">removed</span> <span class="st">&#39;linux-source-code-dupe8/linux-master/drivers/pinctrl/realtek&#39;</span></span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a><span class="ex">real</span>    2m25.713s</span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a><span class="ex">user</span>    2m55.189s</span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a><span class="ex">sys</span>     0m52.527s</span></code></pre></div>
<p>You can also compare things that aren’t time ordered.
Just keep in mind the changes will flip depending which you put first.</p>
<h1 id="minimal-find-command">Minimal find command</h1>
<p>Once I started hashing my drives + tarballs and keeping <code>.bigtree</code> files
indexing their contents, I realised I could also use them to find particular
files without having the drives on hand. It may not sound like a big upgrade vs
<code>find</code>, <code>locate</code>, or similar commands, but it’s come to be an essential part of
my workflow.</p>
<p>Interactive use is a bit like <code>find</code> or <code>tar --list</code>.</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode .bash"><code class="sourceCode bash"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> bigtrees find test-files.bigtree <span class="at">--search-regex</span> <span class="st">&#39;/old.*\.jpg$&#39;</span></span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/josef-friedrichj/test-files-master/jpg/old-house.jpg</span></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/josef-friedrichj-dupe1/test-files-master/jpg/old-house.jpg</span></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/josef-friedrichj-dupe2/test-files-master/jpg/old-house.jpg</span></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/josef-friedrichj-dupe3/test-files-master/jpg/old-house.jpg</span></span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/josef-friedrichj-dupe4/test-files-master/jpg/old-house.jpg</span></span>
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/josef-friedrichj-dupe5/test-files-master/jpg/old-house.jpg</span></span>
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/josef-friedrichj-dupe6/test-files-master/jpg/old-house.jpg</span></span>
<span id="cb6-10"><a href="#cb6-10" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/josef-friedrichj-dupe7/test-files-master/jpg/old-house.jpg</span></span>
<span id="cb6-11"><a href="#cb6-11" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/josef-friedrichj-dupe8/test-files-master/jpg/old-house.jpg</span></span>
<span id="cb6-12"><a href="#cb6-12" aria-hidden="true" tabindex="-1"></a><span class="ex">test-files/josef-friedrichj-dupe9/test-files-master/jpg/old-house.jpg</span></span></code></pre></div>]]></summary>
</entry>
<entry>
    <title>How ElectionGuard counts encrypted votes</title>
    <link href="https://cryptoisland.blog/posts/2024/12/10/how-electionguard-counts-encrypted-votes" />
    <id>https://cryptoisland.blog/posts/2024/12/10/how-electionguard-counts-encrypted-votes</id>
    <published>2024-12-10T00:00:00Z</published>
    <updated>2025-10-27</updated>
    <summary type="html"><![CDATA[
<div class="toc"><div class="header">Contents</div>
<ul>
<li><a href="#ignore-the-asymmetric-encryption" id="toc-ignore-the-asymmetric-encryption">Ignore the asymmetric encryption</a></li>
<li><a href="#homomorphic-addition" id="toc-homomorphic-addition">Homomorphic addition</a></li>
<li><a href="#multiple-options-per-ballot" id="toc-multiple-options-per-ballot">Multiple options per ballot</a></li>
<li><a href="#zero-knowledge-proofs" id="toc-zero-knowledge-proofs">Zero knowledge proofs</a></li>
<li><a href="#put-it-all-together" id="toc-put-it-all-together">Put it all together</a></li>
</ul>
<center class="reminder"><img src="electionguard-tally-02.png"></img></center></div>
<p>How is it possible for ElectionGuard to add up votes,
and guarantee the final tally is accurate,
without being able to read any of the individual ballots?</p>
<h1 id="ignore-the-asymmetric-encryption">Ignore the asymmetric encryption</h1>
<p>This is also really cool, but I’ll gloss over it for today because it’s
ubiquitous on the internet and there are lots of explanations available.
You can think of it as standard public key encryption:
the voting machine encrypts each ballot to the public key generated by the guardians during the key ceremony.
Then they decrypt the final tally with with their (shared) private key.</p>
<p>Instead, today I want to go over what happens inbetween during <b>step 2</b>:
How do all the encrypted ballots become one big final encrypted tally?</p>
<!-- TODO do separate posts for parts 1 and 3 here and link to them -->
<table>
<tr>
<td>
<img src="electionguard-tally-01.png"></img>
<center>
<ol type="1">
<li>Voting machine encrypts ballots
</center>
</td>
<td>
<img src="electionguard-tally-02.png"></img>
<center>
<ol start="2" type="1">
<li>???
</center>
</td>
<td>
<img src="electionguard-tally-03.png"></img>
<center>
<ol start="3" type="1">
<li>Guardians decrypt final tally
</center>
</td>
</tr>
</table></li>
</ol></li>
</ol></li>
</ol>
<h1 id="homomorphic-addition">Homomorphic addition</h1>
<p>The trick is that the encrypted votes are encoded as exponents,
and exponents have a cool property: multiplying the same base number with two different exponents is equivalent to adding the exponents.</p>
<p>You can get some intuition for it by playing around in any calculator app or programming language interpreter.
Here I’m using R.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode R"><code class="sourceCode r"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="sc">&gt;</span> <span class="dv">10</span><span class="sc">^</span><span class="dv">1</span> <span class="sc">*</span> <span class="dv">10</span><span class="sc">^</span><span class="dv">1</span> <span class="sc">==</span> <span class="dv">10</span><span class="sc">^</span>(<span class="dv">1</span><span class="sc">+</span><span class="dv">1</span>)</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a>[<span class="dv">1</span>] <span class="cn">TRUE</span></span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="sc">&gt;</span> <span class="dv">10</span><span class="sc">^</span><span class="dv">1</span> <span class="sc">*</span> <span class="dv">10</span><span class="sc">^</span><span class="dv">0</span> <span class="sc">==</span> <span class="dv">10</span><span class="sc">^</span>(<span class="dv">1</span><span class="sc">+</span><span class="dv">0</span>)</span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a>[<span class="dv">1</span>] <span class="cn">TRUE</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="sc">&gt;</span> <span class="dv">10</span><span class="sc">^</span><span class="dv">5</span> <span class="sc">*</span> <span class="dv">10</span><span class="sc">^</span><span class="dv">7</span> <span class="sc">==</span> <span class="dv">10</span><span class="sc">^</span>(<span class="dv">5</span><span class="sc">+</span><span class="dv">7</span>)</span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>[<span class="dv">1</span>] <span class="cn">TRUE</span></span></code></pre></div>
<p>Of course the encrypted votes aren’t in base 10.
They’re in base some-huge-number-you-can’t-guess, which is what keeps you from reading them!
But the principle stays the same and, importantly, you can do the multiplication without knowing the base.
This is the property we want:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode txt"><code class="sourceCode default"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a>encrypt(a) * encrypt(b) = encrypt(a + b)</span></code></pre></div>
<p>For example, say the secret base number is 34. Here’s how we could encrypt and decrypt with it:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode R"><code class="sourceCode r"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a>encrypt <span class="ot">&lt;-</span> <span class="cf">function</span>(vote)</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>  <span class="co"># to encrypt, raise 34 to the `vote`th power</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a>  <span class="dv">34</span> <span class="sc">^</span> vote</span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>  </span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a>decrypt <span class="ot">&lt;-</span> <span class="cf">function</span>(vote)</span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a>  <span class="co"># to decrypt, take the base-34 logarithm</span></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a>  <span class="fu">log</span>(vote, <span class="at">base=</span><span class="dv">34</span>)</span></code></pre></div>
<div class="sourceCode" id="cb4"><pre class="sourceCode R"><code class="sourceCode r"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="sc">&gt;</span> <span class="fu">encrypt</span>(<span class="dv">5</span>)</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a>[<span class="dv">1</span>] <span class="dv">45435424</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a><span class="sc">&gt;</span> <span class="fu">encrypt</span>(<span class="dv">7</span>)</span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>[<span class="dv">1</span>] <span class="dv">52523350144</span></span></code></pre></div>
<p>Now without knowing the base OR the encrypted values, someone else can still find their product,
and can know that’s the encryption of the sum of the encrypted values.</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode R"><code class="sourceCode r"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="sc">&gt;</span> <span class="dv">45435424</span> <span class="sc">*</span> <span class="dv">52523350144</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a>[<span class="dv">1</span>] <span class="fl">2.386421e+18</span></span></code></pre></div>
<p>Since we know the secret base is 34, we can also get the sum back:</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode R"><code class="sourceCode r"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="sc">&gt;</span> <span class="fu">decrypt</span>(<span class="fl">2.386421e+18</span>)</span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a>[<span class="dv">1</span>] <span class="dv">12</span></span></code></pre></div>
<p>Brilliant!
Now, there are a couple more nuances that aren’t hard to add to our example…</p>
<h1 id="multiple-options-per-ballot">Multiple options per ballot</h1>
<p>Each ballot is really a data structure with an encrypted number per candidate/option.
The main election config says which ones belong to which contests.
Let’s say our ballot has two contests, one with two options and one with three.
It could be represented in R using a vector:</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode txt"><code class="sourceCode default"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a>c(n,n , n,n,n)</span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a>  ___   _____</span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a>   |      |</span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a>   |      contest 2</span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a>   |</span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a>   contest 1</span></code></pre></div>
<p>(One reason to use R today is we don’t have to change our encrypt/decrypt functions. They’ll work on vectors automatically.)</p>
<h1 id="zero-knowledge-proofs">Zero knowledge proofs</h1>
<p>The other nuance is that each encrypted number should either be a <code>1</code> or a <code>0</code>.
Most of the data in real ballots is made of zero knowledge proofs (out of scope for today) to guarantee that each number is a <code>1</code> or a <code>0</code> without revealing which!
Otherwise, it would be possible to cheat by creating one ballot with huge numbers like <code>c(10000, -10000, ...)</code>.</p>
<h1 id="put-it-all-together">Put it all together</h1>
<p>Anyway, using our same functions from above, a little more realistic run-through might look like this.</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode R"><code class="sourceCode r"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="co"># 6 ballots with two contests each</span></span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a><span class="co"># (contest 1 has 2 options and contest 2 has 3)</span></span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a>b1 <span class="ot">&lt;-</span> <span class="fu">c</span>(<span class="dv">1</span>,<span class="dv">0</span> , <span class="dv">1</span>,<span class="dv">0</span>,<span class="dv">0</span>)</span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a>b2 <span class="ot">&lt;-</span> <span class="fu">c</span>(<span class="dv">1</span>,<span class="dv">0</span> , <span class="dv">1</span>,<span class="dv">0</span>,<span class="dv">0</span>)</span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a>b3 <span class="ot">&lt;-</span> <span class="fu">c</span>(<span class="dv">0</span>,<span class="dv">1</span> , <span class="dv">1</span>,<span class="dv">0</span>,<span class="dv">0</span>)</span>
<span id="cb8-6"><a href="#cb8-6" aria-hidden="true" tabindex="-1"></a>b4 <span class="ot">&lt;-</span> <span class="fu">c</span>(<span class="dv">0</span>,<span class="dv">1</span> , <span class="dv">0</span>,<span class="dv">1</span>,<span class="dv">0</span>)</span>
<span id="cb8-7"><a href="#cb8-7" aria-hidden="true" tabindex="-1"></a>b5 <span class="ot">&lt;-</span> <span class="fu">c</span>(<span class="dv">0</span>,<span class="dv">1</span> , <span class="dv">0</span>,<span class="dv">1</span>,<span class="dv">0</span>)</span>
<span id="cb8-8"><a href="#cb8-8" aria-hidden="true" tabindex="-1"></a>b6 <span class="ot">&lt;-</span> <span class="fu">c</span>(<span class="dv">0</span>,<span class="dv">1</span> , <span class="dv">0</span>,<span class="dv">0</span>,<span class="dv">1</span>)</span>
<span id="cb8-9"><a href="#cb8-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-10"><a href="#cb8-10" aria-hidden="true" tabindex="-1"></a><span class="co"># encryption done by voting machines</span></span>
<span id="cb8-11"><a href="#cb8-11" aria-hidden="true" tabindex="-1"></a>e1 <span class="ot">&lt;-</span> <span class="fu">encrypt</span>(b1)</span>
<span id="cb8-12"><a href="#cb8-12" aria-hidden="true" tabindex="-1"></a>e2 <span class="ot">&lt;-</span> <span class="fu">encrypt</span>(b2)</span>
<span id="cb8-13"><a href="#cb8-13" aria-hidden="true" tabindex="-1"></a>e3 <span class="ot">&lt;-</span> <span class="fu">encrypt</span>(b3)</span>
<span id="cb8-14"><a href="#cb8-14" aria-hidden="true" tabindex="-1"></a>e4 <span class="ot">&lt;-</span> <span class="fu">encrypt</span>(b4)</span>
<span id="cb8-15"><a href="#cb8-15" aria-hidden="true" tabindex="-1"></a>e5 <span class="ot">&lt;-</span> <span class="fu">encrypt</span>(b5)</span>
<span id="cb8-16"><a href="#cb8-16" aria-hidden="true" tabindex="-1"></a>e6 <span class="ot">&lt;-</span> <span class="fu">encrypt</span>(b6)</span>
<span id="cb8-17"><a href="#cb8-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-18"><a href="#cb8-18" aria-hidden="true" tabindex="-1"></a><span class="co"># guardian tally ceremony,</span></span>
<span id="cb8-19"><a href="#cb8-19" aria-hidden="true" tabindex="-1"></a><span class="co"># including homomorphic addition</span></span>
<span id="cb8-20"><a href="#cb8-20" aria-hidden="true" tabindex="-1"></a>eSums <span class="ot">&lt;-</span> e1 <span class="sc">*</span> e2 <span class="sc">*</span> e3 <span class="sc">*</span> e4 <span class="sc">*</span> e5 <span class="sc">*</span> e6</span>
<span id="cb8-21"><a href="#cb8-21" aria-hidden="true" tabindex="-1"></a><span class="fu">decrypt</span>(eSums)</span>
<span id="cb8-22"><a href="#cb8-22" aria-hidden="true" tabindex="-1"></a><span class="co"># [1] 2 4 3 2 1</span></span>
<span id="cb8-23"><a href="#cb8-23" aria-hidden="true" tabindex="-1"></a><span class="co">#     ___ _____</span></span>
<span id="cb8-24"><a href="#cb8-24" aria-hidden="true" tabindex="-1"></a><span class="co">#      |    |</span></span>
<span id="cb8-25"><a href="#cb8-25" aria-hidden="true" tabindex="-1"></a><span class="co">#      |    contest 2 results</span></span>
<span id="cb8-26"><a href="#cb8-26" aria-hidden="true" tabindex="-1"></a><span class="co">#      |</span></span>
<span id="cb8-27"><a href="#cb8-27" aria-hidden="true" tabindex="-1"></a><span class="co">#      contest 1 results</span></span></code></pre></div>
<p><img src="electionguard-tally-04.png"></img></p>
<p>Cool, right?? It’s as if we added up the unencrypted ballots column-wise.</p>
<p>This has been a simplified example, but I hope it provides some intuition—and reassurance—about
one of the big questions you might have as a skeptical voter.
I suspect that explaining stuff like this well will play an important role in getting election systems upgraded over the medium-to-long term.</p>]]></summary>
</entry>
<entry>
    <title>ElectionGuard + blockchain integration ideas</title>
    <link href="https://cryptoisland.blog/posts/2024/10/07/electionguard-blockchain-integration-ideas" />
    <id>https://cryptoisland.blog/posts/2024/10/07/electionguard-blockchain-integration-ideas</id>
    <published>2024-10-07T00:00:00Z</published>
    <updated>2025-10-27</updated>
    <summary type="html"><![CDATA[
<div class="toc">
<center class="reminder"><img src="please-lock-door.png"></img></center></div>
<p>Welcome to the first in an ongoing series of posts about <a href="https://www.electionguard.vote/">ElectionGuard</a>!
Some will explain how it works now, and some will also include ways I think it could be extended and integrated with a blockchain. I’ll try to differentiate clearly between the two.</p>
<p>For now I mainly want to emphasize that although blockchains and related tech aren’t required,
they could be used to strengthen the security and trustworthiness of almost every step.
When dealing with public elections, it’s reasonable to add some redundant safeguards!</p>
<p>To read a linear series of posts, click the “electionguard” tag at the top.
To jump to particular topics instead, use the links below.</p>
<table>
<caption>Who’s responsible for each part of an election, and which tools do they use?</caption>
<colgroup>
<col style="width: 19%" />
<col style="width: 23%" />
<col style="width: 30%" />
<col style="width: 26%" />
</colgroup>
<thead>
<tr>
<th>Action</th>
<th>EG in Theory</th>
<th>EG in Practice</th>
<th>My Suggestion</th>
</tr>
</thead>
<tbody>
<tr>
<td>set election parameters</td>
<td>administrator via website + deployments</td>
<td>administrator via website + deployments</td>
<td>administrator via <b>blockchain</b></td>
</tr>
<tr>
<td>set up physical infrastructure</td>
<td>administrator</td>
<td>administrator</td>
<td>administrator?</td>
</tr>
<tr>
<td>manage bulletin board</td>
<td>administrator</td>
<td>administrator</td>
<td><b>blockchain</b></td>
</tr>
<tr>
<td>host bulletin board</td>
<td>administrator via website</td>
<td>administrator via website</td>
<td><b>decentralized</b> quardians + anyone via <b>IPFS</b></td>
</tr>
<tr>
<td>publish live stats during election</td>
<td>NA</td>
<td>anyone on own websites</td>
<td>anyone on own websites</td>
</tr>
<tr>
<td>perform key ceremony</td>
<td>independent guardians</td>
<td>guardians following administrator</td>
<td><b>decentralized</b> guardians</td>
</tr>
<tr>
<td>publish ballots, mark cast or spoiled</td>
<td>mediators via https?</td>
<td>mediators via https?</td>
<td>mediators (and <a href="http://localhost:8000/posts/2024/10/15/mechanics-of-the-benaloh-challenge/#self-certify-casts-audits">voters</a>?) via <b>blockchain</b> + <b>IPFS</b></td>
</tr>
<tr>
<td>verify inclusion of own vote</td>
<td>voter via website</td>
<td>voter via website</td>
<td><a href="/posts/2024/10/15/mechanics-of-the-benaloh-challenge/#cast-audit-via-phone-app">voter via <b>blockchain + IPFS</b></a></td>
</tr>
<tr>
<td>certify or dispute inclusion of own vote</td>
<td>NA</td>
<td>NA</td>
<td><a href="http://localhost:8000/posts/2024/10/15/mechanics-of-the-benaloh-challenge/#self-certify-casts-audits">voter via <b>blockchain</b></a></td>
</tr>
<tr>
<td>certify or dispute spoiled ballot decryption</td>
<td>NA</td>
<td>NA</td>
<td><a href="/posts/2024/10/15/mechanics-of-the-benaloh-challenge/#cast-audit-via-phone-app">voter via <b>blockchain</b></a></td>
</tr>
<tr>
<td>gather artifacts to verify</td>
<td>anyone via website</td>
<td>anyone via website + news/social media</td>
<td>anyone via <b>blockchain + IPFS</b></td>
</tr>
<tr>
<td>publish verification</td>
<td>anyone via news/social media</td>
<td>anyone via news/social media</td>
<td>anyone via <b>blockchain</b></td>
</tr>
<tr>
<td>publish fraud proofs</td>
<td>NA</td>
<td>anyone via news/social media</td>
<td>anyone via <b>blockchain</b></td>
</tr>
<tr>
<td>perform audits</td>
<td>administrator + quorum of independent guardians</td>
<td>administrator + quorum of loyal guardians</td>
<td>administrator + quorum of <b>decentralized</b> guardians</td>
</tr>
<tr>
<td>tally encrypted votes</td>
<td>quorum of independent guardians</td>
<td>quorum of loyal guardians</td>
<td>quorum of <b>decentralized</b> guardians</td>
</tr>
<tr>
<td>decrypt votes during audits</td>
<td>quorum of independent guardians</td>
<td>quorum of loyal guardians</td>
<td>quorum of <b>decentralized</b> guardians using <b>blockchain random seed</b></td>
</tr>
<tr>
<td>publish tally + audit results</td>
<td>administrator via website?</td>
<td>administrator via website?</td>
<td><b>decentralized</b> guardians via <b>blockchain</b> + <b>IPFS</b></td>
</tr>
</tbody>
</table>]]></summary>
</entry>
<entry>
    <title>Derive Cardano staking and receive addresses offline</title>
    <link href="https://cryptoisland.blog/posts/2025/10/14/offline-ada-addresses" />
    <id>https://cryptoisland.blog/posts/2025/10/14/offline-ada-addresses</id>
    <published>2025-10-14T00:00:00Z</published>
    <updated>2025-10-14T00:00:00Z</updated>
    <summary type="html"><![CDATA[
<div class="toc">
</div>
<p>This could be useful for a variety of purposes, but the immediate reason to post it is for anyone who has only their seed phrase (no synced online wallet), and wants to claim their NIGHT from the Midnight Glacier Drop.
If that’s you, make yourself a NixOS live USB according to <a href="/posts/2025/10/11/mngd-offline-signing-scripts-ada-eth">my earlier post</a>.
Then download and run <a href="./offline-ada-addrs.tar">this code</a> in the live OS to get your addresses…</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode .bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="co"># my scripts</span></span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="fu">tar</span> <span class="at">-xvf</span> offline-ada-addrs.tar</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="bu">cd</span> offline-ada-addrs</span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="co"># cardano-wallet</span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="va">tarball</span><span class="op">=</span><span class="st">&#39;cardano-wallet-v2025-03-31-linux64.tar.gz&#39;</span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="ex">curl</span> <span class="at">-L</span> <span class="at">-O</span> <span class="st">&quot;https://github.com/cardano-foundation/cardano-wallet/releases/download/v2025-03-31/</span><span class="va">${tarball}</span><span class="st">&quot;</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a><span class="fu">tar</span> <span class="at">-xvf</span> <span class="st">&quot;</span><span class="va">$tarball</span><span class="st">&quot;</span></span>
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a><span class="co"># DISCONNECT NETWORK HERE</span></span>
<span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a><span class="co"># transfer files in</span></span>
<span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a><span class="fu">cp</span> /your/usb/drive/ada-seeds/<span class="pp">*</span>-seed.txt ./seeds/</span>
<span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a><span class="co"># run the script</span></span>
<span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a><span class="ex">./main.sh</span></span>
<span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-18"><a href="#cb1-18" aria-hidden="true" tabindex="-1"></a><span class="co"># transfer files out</span></span>
<span id="cb1-19"><a href="#cb1-19" aria-hidden="true" tabindex="-1"></a><span class="fu">cp</span> addrs/<span class="pp">*</span>-addrs.txt /your/usb/drive/ada-addrs/</span>
<span id="cb1-20"><a href="#cb1-20" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb1-21"><a href="#cb1-21" aria-hidden="true" tabindex="-1"></a><span class="co"># wipe seed phrases before leaving</span></span>
<span id="cb1-22"><a href="#cb1-22" aria-hidden="true" tabindex="-1"></a><span class="fu">shred</span> <span class="at">-n3</span> <span class="at">-u</span> seeds/<span class="pp">*</span></span></code></pre></div>
<p>The output files should look like this,
and the addresses should match what you would see in most wallets.
You can check them with a block explorer like <a href="https://cardanoscan.io">CardanoScan</a> or <a href="https://adastat.net">AdaStat</a>.</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode .txt"><code class="sourceCode default"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a>wallet name:</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>WALLETNAME</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a>stake address:</span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a>stake1u...</span>
<span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a>receive addresses:</span>
<span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a>addr1q...</span>
<span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a>addr1q...</span>
<span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a>addr1q...</span>
<span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a>...</span></code></pre></div>
<p>From there you can go back and follow the rest of <a href="/posts/2025/10/11/mngd-offline-signing-scripts-ada-eth">the earlier post</a>.
You’ll need to get your claim messages from the portal, then boot back into NixOS to sign them,
then go back to the portal a second time to submit everything.</p>]]></summary>
</entry>
<entry>
    <title>Midnight Glacier Drop offline signing scripts for ADA and ETH</title>
    <link href="https://cryptoisland.blog/posts/2025/10/11/mngd-offline-signing-scripts-ada-eth" />
    <id>https://cryptoisland.blog/posts/2025/10/11/mngd-offline-signing-scripts-ada-eth</id>
    <published>2025-10-11T00:00:00Z</published>
    <updated>2025-10-11T00:00:00Z</updated>
    <summary type="html"><![CDATA[
<div class="toc"><div class="header">Contents</div>
<ul>
<li><a href="#cardano-workflow" id="toc-cardano-workflow">Cardano workflow</a>
<ul>
<li><a href="#gather-info" id="toc-gather-info">Gather info</a></li>
<li><a href="#make-a-nixos-live-usb" id="toc-make-a-nixos-live-usb">Make a NixOS live USB</a></li>
<li><a href="#set-up-offline-environment" id="toc-set-up-offline-environment">Set up offline environment</a></li>
<li><a href="#sign-claim-messages" id="toc-sign-claim-messages">Sign claim messages</a></li>
<li><a href="#submit-claims" id="toc-submit-claims">Submit claims</a></li>
</ul></li>
<li><a href="#ethereum-workflow" id="toc-ethereum-workflow">Ethereum workflow</a></li>
</ul>
<center class="reminder"><img src="pocketwatch.png"></img></center></div>
<p>Today we’ll be signing Glacier Drop claims offline with <a href="https://github.com/gitmachtl/cardano-signer">cardano-signer</a> and/or <a href="https://docs.ethers.org/v6/">ethers</a>.
I’m not claiming this is the only or best way; it’s just a cleaned up version of what I did.
Please let me know ASAP if you notice anything dumb or insecure!</p>
<p>This guide assumes you have your wallet(s) synced up already,
but they aren’t compatible with the Glacier Drop.
Maybe you still use Daedalus and don’t want to additionally trust a web wallet with your seed phrases, for example.</p>
<p><em>If you <em>only</em> have the seed phrases and no wallet set up,
you’ll need to make a wallet or calculate a suitable destination address before making a claim message.
<del>That’s out of scope for this post but I’ll write an update explaining it if anyone asks.</del> See <a href="/posts/2025/10/14/offline-ada-addresses">this post</a> for instructions.</em></p>
<p>You’ll need two USB drives.</p>
<h1 id="cardano-workflow">Cardano workflow</h1>
<h2 id="gather-info">Gather info</h2>
<p>If you have more than a couple wallets I recommend starting a spreadsheet to keep track:
wallet name, stake address, destination address, night allocation, thaw dates (fill in later).
You also need to make two text files per wallet: <code>WALLETNAME-claim.txt</code> for the unique claim message, and <code>WALLETNAME-seed.txt</code> for the seed (recovery phrase).</p>
<p>To get each claim message you’ll need to go through the <a href="https://claim.midnight.gd">claim portal</a> wizard up to the point where you’re supposed to sign.
Instead, save the message to the text file and start over with the next wallet.</p>
<p><em>YMMV, but I had to use Chrome (Chromium) because there were lots of JS errors in Firefox, and it wasn’t able to download the PDF receipt at the end. Guess IOG isn’t supporting it?</em></p>
<p>Personally, I used the same unused receive address as both source and destination for each wallet.
The portal can figure out the stake address from there.
Then I double checked that it matched the stake address in the spreadsheet.
It would also be fine to set a different destination address if you want to gather your NIGHT in one wallet rather than keeping them separate.</p>
<p>The claims should look like this.</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode .txt"><code class="sourceCode default"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a>STAR XXXXX to addr1qXXX...XXX 31a...b8b</span></code></pre></div>
<p>Put them on some removable media to move to your offline signing environment.
Find your seed phrases too.</p>
<h2 id="make-a-nixos-live-usb">Make a NixOS live USB</h2>
<p><em>This can be another distro if you already have a favorite.
See <code>shell.nix</code> in each tarball for approximate packages and JS setup commands.</em></p>
<div class="sourceCode" id="cb2"><pre class="sourceCode .bash"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="va">iso_url</span><span class="op">=</span><span class="st">&#39;https://channels.nixos.org/nixos-25.05/latest-nixos-graphical-x86_64-linux.iso&#39;</span></span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="fu">wget</span> <span class="st">&quot;</span><span class="va">${iso_url}</span><span class="st">.sha256&quot;</span></span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="fu">wget</span> <span class="st">&quot;</span><span class="va">${iso_url}</span><span class="st">&quot;</span></span>
<span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="fu">sha256sum</span> <span class="at">-c</span> latest-nixos-graphical-x86_64-linux.iso.sha256</span>
<span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a><span class="fu">sudo</span> dd if=latest-nixos-graphical-x86_64-linux.iso of=/dev/YOURDEVHERE status=progress <span class="kw">&amp;&amp;</span> <span class="fu">sudo</span> sync</span></code></pre></div>
<p><em>Careful that you have the <code>dd</code> command worked out properly to avoid data destruction,
or write the ISO a different way.</em></p>
<h2 id="set-up-offline-environment">Set up offline environment</h2>
<p>Boot into your live USB/DVD.
Set up networking via ethernet if possible, or wifi if not.
You want to be able to definitively disconnect it after installing the code.</p>
<p>Download and unpack <a href="./mngd-offline-sign-ada.tar">this tarball</a>,
then set up and test <a href="https://github.com/gitmachtl/cardano-signer">cardano-signer</a>:</p>
<div class="sourceCode" id="cb3"><pre class="sourceCode .bash"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="fu">tar</span> <span class="at">-xvf</span> mngd-offline-sign-ada.tar</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="bu">cd</span> mngd-offline-sign-ada</span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="fu">git</span> clone https://github.com/gitmachtl/cardano-signer</span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="fu">mv</span> shell.nix cardano-signer/src/</span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a><span class="bu">cd</span> cardano-signer/src</span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="ex">nix-shell</span></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a><span class="bu">cd</span> ../..</span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a><span class="co"># check that it works</span></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a><span class="ex">cardano-signer</span> <span class="kw">|</span> <span class="fu">less</span> <span class="at">-R</span></span></code></pre></div>
<p>Now disconnect the network!
(Install ETH code first too if needed)
Ideally you want to unplug the ethernet cable,
or maybe turn off your router?
If you’re less paranoid, something like this might also be good enough:</p>
<div class="sourceCode" id="cb4"><pre class="sourceCode .bash"><code class="sourceCode bash"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="fu">sudo</span> systemctl stop NetworkManager</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="fu">sudo</span> rm <span class="at">-rf</span> /etc/wpa_supplicant</span></code></pre></div>
<h2 id="sign-claim-messages">Sign claim messages</h2>
<p>There are several scripts and sets of files,
but all the info you need to keep ends up in <code>portals/*.txt</code>.</p>
<div class="sourceCode" id="cb5"><pre class="sourceCode .bash"><code class="sourceCode bash"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="co"># DISCONNECT NETWORK HERE</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="co"># transfer files in</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="fu">cp</span> /your/usb/drive/ada-seeds/<span class="pp">*</span>-seed.txt   ./seeds/</span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="fu">cp</span> /your/usb/drive/ada-claims/<span class="pp">*</span>-claim.txt ./claims/</span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a><span class="co"># run the scripts</span></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a><span class="ex">./1-keys.sh</span></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a><span class="ex">./2-sign.sh</span> <span class="co"># </span><span class="al">TODO</span><span class="co"> adjust if needed</span></span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a><span class="ex">./3-portal.sh</span></span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a><span class="co"># transfer files out</span></span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a><span class="fu">cp</span> portal/<span class="pp">*</span>-portal-info.txt /your/usb/drive/</span>
<span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-15"><a href="#cb5-15" aria-hidden="true" tabindex="-1"></a><span class="co"># optionally clean up before rebooting</span></span>
<span id="cb5-16"><a href="#cb5-16" aria-hidden="true" tabindex="-1"></a><span class="ex">./4-shred.sh</span></span></code></pre></div>
<p><em>If you’re gathering all your NIGHT to one wallet,
comment out the line in <code>2-sign.sh</code> that asserts each destination address
belongs to the wallet generated from the corresponding seed phrase.</em></p>
<h2 id="submit-claims">Submit claims</h2>
<p>The last step is to go back to <a href="https://claim.midnight.gd">the portal</a> on your online computer.
Click through the wizard again for each wallet, giving the exact same answers to get the same claim message.
If you use a different destination address each time,
make sure it isn’t auto-filling the previous one.
Double check that everything matches before submitting: destination address, allocation, claim message, stake key.</p>
<p>Each portal info file should look like this.</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode .txt"><code class="sourceCode default"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a>midnight glacier drop claim info for WALLETNAME wallet</span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a>network:</span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a>cardano</span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a>destination addr:</span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a>addr1qXXX...XXX</span>
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true" tabindex="-1"></a>claim message:</span>
<span id="cb6-10"><a href="#cb6-10" aria-hidden="true" tabindex="-1"></a>STAR XXXXX to addr1qXXX...XXX 31a...b8b</span>
<span id="cb6-11"><a href="#cb6-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-12"><a href="#cb6-12" aria-hidden="true" tabindex="-1"></a>signature:</span>
<span id="cb6-13"><a href="#cb6-13" aria-hidden="true" tabindex="-1"></a>XXXXXXXXXXXXXXXXXXXXXXXXXXXXX...</span>
<span id="cb6-14"><a href="#cb6-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb6-15"><a href="#cb6-15" aria-hidden="true" tabindex="-1"></a>pubkey:</span>
<span id="cb6-16"><a href="#cb6-16" aria-hidden="true" tabindex="-1"></a>XXXXXXXXXXXXXXXXXXXXXXXXXXXXX...</span></code></pre></div>
<p>Paste the signature and pubkey, then sign + complete the claim.
I saved the PDF receipts and took screenshots too for good measure.</p>
<p><em>Don’t forget to also get rid of any temporary copies of your seed phrases,
especially on the USB drive. <code>shred -n3 -u</code> is an easy way.</em></p>
<h1 id="ethereum-workflow">Ethereum workflow</h1>
<p>The process for ETH is similar, except that you need to create a Cardano wallet to generate your destination address(es) first.
You could do that offline, but I assume most people won’t bother; making an empty web wallet is easy and low risk.</p>
<p>The other difference is it works with 3 kinds of keys:</p>
<ol type="1">
<li><code>WALLETNAME-seed.txt</code></li>
<li><code>WALLETNAME-keystore.json</code> + <code>WALLETNAME-password.txt</code></li>
<li><code>WALLETNAME-privatekey.txt</code></li>
</ol>
<p>Here’s <a href="./mngd-offline-sign-eth.tar">the tarball</a>.</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode .bash"><code class="sourceCode bash"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="co"># make a live USB and boot into NixOS as above</span></span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a><span class="co"># install the code</span></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a><span class="fu">tar</span> <span class="at">-xvf</span> mngd-offline-sign-eth.tar</span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a><span class="bu">cd</span> mngd-offline-sign-eth</span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a><span class="ex">nix-shell</span></span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a><span class="co"># DISCONNECT NETWORK HERE</span></span>
<span id="cb7-9"><a href="#cb7-9" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-10"><a href="#cb7-10" aria-hidden="true" tabindex="-1"></a><span class="co"># transfer files in</span></span>
<span id="cb7-11"><a href="#cb7-11" aria-hidden="true" tabindex="-1"></a><span class="fu">cp</span> /your/usb/drive/eth-keys/<span class="pp">*</span> ./seeds-and-keys/</span>
<span id="cb7-12"><a href="#cb7-12" aria-hidden="true" tabindex="-1"></a><span class="fu">cp</span> /your/usb/drive/eth-claims/<span class="pp">*</span>-claim.txt ./claims/</span>
<span id="cb7-13"><a href="#cb7-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-14"><a href="#cb7-14" aria-hidden="true" tabindex="-1"></a><span class="co"># run the script</span></span>
<span id="cb7-15"><a href="#cb7-15" aria-hidden="true" tabindex="-1"></a><span class="ex">./1-main.sh</span></span>
<span id="cb7-16"><a href="#cb7-16" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-17"><a href="#cb7-17" aria-hidden="true" tabindex="-1"></a><span class="co"># transfer files out</span></span>
<span id="cb7-18"><a href="#cb7-18" aria-hidden="true" tabindex="-1"></a><span class="fu">cp</span> portal/<span class="pp">*</span>-portal-info.txt /your/usb/drive/</span>
<span id="cb7-19"><a href="#cb7-19" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb7-20"><a href="#cb7-20" aria-hidden="true" tabindex="-1"></a><span class="co"># optionally clean up before rebooting</span></span>
<span id="cb7-21"><a href="#cb7-21" aria-hidden="true" tabindex="-1"></a><span class="ex">./2-shred.sh</span></span></code></pre></div>
<p>Portal info files should look like this.</p>
<div class="sourceCode" id="cb8"><pre class="sourceCode .txt"><code class="sourceCode default"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a>midnight glacier drop claim info for WALLETNAME wallet</span>
<span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a>network:</span>
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a>ethereum</span>
<span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-6"><a href="#cb8-6" aria-hidden="true" tabindex="-1"></a>origin address:</span>
<span id="cb8-7"><a href="#cb8-7" aria-hidden="true" tabindex="-1"></a>0xXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</span>
<span id="cb8-8"><a href="#cb8-8" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-9"><a href="#cb8-9" aria-hidden="true" tabindex="-1"></a>destination address:</span>
<span id="cb8-10"><a href="#cb8-10" aria-hidden="true" tabindex="-1"></a>addr1qXXX...XXX</span>
<span id="cb8-11"><a href="#cb8-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-12"><a href="#cb8-12" aria-hidden="true" tabindex="-1"></a>claim message:</span>
<span id="cb8-13"><a href="#cb8-13" aria-hidden="true" tabindex="-1"></a>STAR XXXXX to addr1qXXX...XXX 31a...b8b</span>
<span id="cb8-14"><a href="#cb8-14" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb8-15"><a href="#cb8-15" aria-hidden="true" tabindex="-1"></a>signature:</span>
<span id="cb8-16"><a href="#cb8-16" aria-hidden="true" tabindex="-1"></a>XXXXXXXXXXXXXXXXXXXXXXXXXXXXX...</span></code></pre></div>]]></summary>
</entry>
<entry>
    <title>Flake-based recipe for offline dev shells in NixOS</title>
    <link href="https://cryptoisland.blog/posts/2024/12/09/flake-based-recipe-for-offline-dev-shells-in-nixos" />
    <id>https://cryptoisland.blog/posts/2024/12/09/flake-based-recipe-for-offline-dev-shells-in-nixos</id>
    <published>2024-12-09T00:00:00Z</published>
    <updated>2024-12-09T00:00:00Z</updated>
    <summary type="html"><![CDATA[
<div class="toc"><div class="header">Contents</div>
<ul>
<li><a href="#usage" id="toc-usage">Usage</a></li>
<li><a href="#main-nixos-flake" id="toc-main-nixos-flake">Main NixOS flake</a></li>
<li><a href="#flake-per-repo" id="toc-flake-per-repo">Flake per repo</a>
<ul>
<li><a href="#wrapperscript" id="toc-wrapperscript">wrapperScript</a></li>
<li><a href="#devshell" id="toc-devshell">devShell</a></li>
</ul></li>
</ul>
<center class="reminder"><img src="nix-bread.png"></img></center></div>
<p>I first fell in love with Nix when my mother used to make home cooked meals back on the family farm.
The smell of fresh bread would waft from the kitchen out onto the porch,
gently reminding me to look up from my laptop. I noticed the grass and the insects, and the lazy sound of a plane overhead. Sunlight slanted sideways now, illuminating the garden. I’d been struggling with my vim config all afternoon.
“Just a minute, mom!” I would yell.
“I’ve almost got this infinite recursion bug figured out!”</p>
<p>Just kidding… mostly.
Anyway here’s my recipe for homemade named shells like <code>cryptoisland-shell</code>.
I’ll illustrate it with <a href="https://github.com/jefdaj/cryptoisland/tree/master">the code for this blog</a>,
but it should work for any nix shell.</p>
<p>The main benefit is that after a <code>nixos-rebuild</code> you can work on any of your repos without a network connection. It also makes it easier to update nixpkgs everywhere at once, and lets you garbage collect more aggressively without re-fetching some of the packages later.</p>
<h1 id="usage">Usage</h1>
<p>You can generally stay offline as long as you don’t update nixpkgs or add new programs.</p>
<p>Rebuild the OS after updating one of your flakes:</p>
<div class="sourceCode" id="cb1"><pre class="sourceCode txt"><code class="sourceCode default"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a># cd /etc/nixos</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a># nix flake lock --update-input cryptoisland</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a># nixos-rebuild switch</span></code></pre></div>
<p>Use the named dev shell:</p>
<div class="sourceCode" id="cb2"><pre class="sourceCode txt"><code class="sourceCode default"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a>$ cd ~/cryptoisland</span>
<span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>$ cryptoisland-shell</span>
<span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a>$ ./build.sh # or whatever</span></code></pre></div>
<h1 id="main-nixos-flake">Main NixOS flake</h1>
<p>There’s a lot of line noise here, as usual with flakes. But basically:</p>
<ol type="1">
<li>Add your repo as an input. You can skip following the top level nixpkgs input if the repo has more specific requirements, and you can use a URL instead of a local path.</li>
<li>Add the <code>wrapperScript</code> output to your system packages list.</li>
</ol>
<div class="sourceCode" id="cb3"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="co"># /etc/nixos/flake.nix</span></span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="op">{</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a>  <span class="va">inputs</span> <span class="op">=</span> <span class="op">{</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>    <span class="va">nixpkgs</span>.<span class="va">url</span> <span class="op">=</span> <span class="st">&quot;github:NixOS/nixpkgs/nixos-23.11&quot;</span><span class="op">;</span></span>
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a>    <span class="co"># the part we&#39;re interested in:</span></span>
<span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a>    <span class="va">cryptoisland</span> <span class="op">=</span> <span class="op">{</span></span>
<span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a>      <span class="va">url</span> <span class="op">=</span> <span class="va">path</span><span class="op">:</span><span class="ss">/home/jefdaj/cryptoisland</span><span class="op">;</span></span>
<span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a>      <span class="va">inputs</span>.<span class="va">nixpkgs</span>.<span class="va">follows</span> <span class="op">=</span> <span class="st">&quot;nixpkgs&quot;</span><span class="op">;</span></span>
<span id="cb3-10"><a href="#cb3-10" aria-hidden="true" tabindex="-1"></a>    <span class="op">};</span></span>
<span id="cb3-11"><a href="#cb3-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-12"><a href="#cb3-12" aria-hidden="true" tabindex="-1"></a>  <span class="op">};</span></span>
<span id="cb3-13"><a href="#cb3-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb3-14"><a href="#cb3-14" aria-hidden="true" tabindex="-1"></a>  <span class="co"># remember to add your flake to the inputs</span></span>
<span id="cb3-15"><a href="#cb3-15" aria-hidden="true" tabindex="-1"></a>  <span class="va">outputs</span> <span class="op">=</span> <span class="op">{</span><span class="va">self</span><span class="op">,</span> <span class="va">nixpkgs</span><span class="op">,</span> <span class="va">cryptoisland</span><span class="op">}</span>@<span class="va">inputs</span><span class="op">:</span> <span class="op">{</span></span>
<span id="cb3-16"><a href="#cb3-16" aria-hidden="true" tabindex="-1"></a>    <span class="va">nixosConfigurations</span> <span class="op">=</span> <span class="op">{</span></span>
<span id="cb3-17"><a href="#cb3-17" aria-hidden="true" tabindex="-1"></a>      <span class="va">myhostname</span> <span class="op">=</span> nixpkgs<span class="op">.</span>lib<span class="op">.</span>nixosSystem <span class="op">{</span></span>
<span id="cb3-18"><a href="#cb3-18" aria-hidden="true" tabindex="-1"></a>        <span class="va">specialArgs</span> <span class="op">=</span> <span class="op">{</span> <span class="kw">inherit</span> <span class="va">inputs</span><span class="op">;</span> <span class="op">};</span></span>
<span id="cb3-19"><a href="#cb3-19" aria-hidden="true" tabindex="-1"></a>        <span class="va">modules</span> <span class="op">=</span> <span class="op">[</span></span>
<span id="cb3-20"><a href="#cb3-20" aria-hidden="true" tabindex="-1"></a>          <span class="co"># see next section</span></span>
<span id="cb3-21"><a href="#cb3-21" aria-hidden="true" tabindex="-1"></a>          <span class="ss">./configuration.nix</span></span>
<span id="cb3-22"><a href="#cb3-22" aria-hidden="true" tabindex="-1"></a>        <span class="op">];</span></span>
<span id="cb3-23"><a href="#cb3-23" aria-hidden="true" tabindex="-1"></a>      <span class="op">};</span></span>
<span id="cb3-24"><a href="#cb3-24" aria-hidden="true" tabindex="-1"></a>  <span class="op">};</span></span>
<span id="cb3-25"><a href="#cb3-25" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<div class="sourceCode" id="cb4"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="co"># /etc/nixos/configuration.nix</span></span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="co"># this is a standard &quot;old style&quot; NixOS config imported into the flake</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="op">{</span> <span class="va">config</span><span class="op">,</span> <span class="va">pkgs</span><span class="op">,</span> <span class="va">lib</span><span class="op">,</span> <span class="va">inputs</span><span class="op">,</span> <span class="op">...</span> <span class="op">}</span>:</span>
<span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a><span class="op">{</span></span>
<span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a>  <span class="va">nix</span>.<span class="va">settings</span>.<span class="va">experimental-features</span> <span class="op">=</span> <span class="op">[</span> <span class="st">&quot;nix-command&quot;</span> <span class="st">&quot;flakes&quot;</span> <span class="op">];</span></span>
<span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a>  <span class="va">environment</span>.<span class="va">systemPackages</span> <span class="op">=</span> <span class="kw">with</span> pkgs<span class="op">;</span> <span class="op">[</span></span>
<span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a>    <span class="co"># our offline dev shells</span></span>
<span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a>    <span class="co"># don&#39;t forget to `nix flake lock --update-input` each of these after working on them</span></span>
<span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a>    inputs<span class="op">.</span>cryptoisland<span class="op">.</span>packages<span class="op">.</span><span class="st">&quot;</span><span class="sc">${</span>pkgs<span class="op">.</span>system<span class="sc">}</span><span class="st">&quot;</span><span class="op">.</span>wrapperScript</span>
<span id="cb4-11"><a href="#cb4-11" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-12"><a href="#cb4-12" aria-hidden="true" tabindex="-1"></a>  <span class="op">];</span></span>
<span id="cb4-13"><a href="#cb4-13" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb4-14"><a href="#cb4-14" aria-hidden="true" tabindex="-1"></a>  <span class="co"># ... rest of config here ...</span></span>
<span id="cb4-15"><a href="#cb4-15" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<h1 id="flake-per-repo">Flake per repo</h1>
<p>All you need for this recipe are <code>devShell</code> and <code>wrapperScript</code> outputs.
The rest of the flake can be formatted differently.</p>
<!-- TODO link to complete flake on github -->
<div class="sourceCode" id="cb5"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="co"># /home/jefdaj/cryptoisland/flake.nix</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="op">{</span></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a>  <span class="va">description</span> <span class="op">=</span> <span class="st">&quot;cryptoisland.blog dev shell&quot;</span><span class="op">;</span></span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a>  <span class="va">inputs</span> <span class="op">=</span> <span class="op">{</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a>    <span class="va">nixpkgs</span>.<span class="va">url</span> <span class="op">=</span> <span class="va">github</span><span class="op">:</span><span class="ss">NixOS/nixpkgs/nixos-unstable</span><span class="op">;</span></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a>  <span class="op">};</span></span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a>  <span class="va">outputs</span> <span class="op">=</span> <span class="op">{</span><span class="va">self</span><span class="op">,</span> <span class="va">nixpkgs</span><span class="op">}</span>:</span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a>    <span class="kw">let</span></span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a>      <span class="va">system</span> <span class="op">=</span> <span class="st">&quot;x86_64-linux&quot;</span><span class="op">;</span></span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a>      <span class="va">pkgs</span> <span class="op">=</span> <span class="bu">import</span> nixpkgs <span class="op">{</span> <span class="kw">inherit</span> <span class="va">system</span><span class="op">;</span> <span class="op">};</span></span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a>    <span class="kw">in</span> <span class="kw">rec</span> <span class="op">{</span></span>
<span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a>      <span class="va">packages</span>.<span class="st">&quot;</span><span class="sc">${</span>system<span class="sc">}</span><span class="st">&quot;</span>.<span class="va">default</span> <span class="op">=</span> packages<span class="op">.</span><span class="st">&quot;</span><span class="sc">${</span>system<span class="sc">}</span><span class="st">&quot;</span><span class="op">.</span>wrapperScript<span class="op">;</span></span>
<span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a>      <span class="va">packages</span>.<span class="st">&quot;</span><span class="sc">${</span>system<span class="sc">}</span><span class="st">&quot;</span>.<span class="va">wrapperScript</span> <span class="op">=</span> <span class="op">{</span> <span class="op">...</span> <span class="op">};</span> <span class="co"># see below</span></span>
<span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a>      <span class="va">devShells</span>.<span class="st">&quot;</span><span class="sc">${</span>system<span class="sc">}</span><span class="st">&quot;</span>.<span class="va">default</span>      <span class="op">=</span> <span class="op">{</span> <span class="op">...</span> <span class="op">};</span> <span class="co"># see below</span></span>
<span id="cb5-15"><a href="#cb5-15" aria-hidden="true" tabindex="-1"></a>  <span class="op">};</span></span>
<span id="cb5-16"><a href="#cb5-16" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div>
<h2 id="wrapperscript">wrapperScript</h2>
<p>This is the unique part. It’s a little ugly but reliable.</p>
<div class="sourceCode" id="cb6"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a>packages<span class="op">.</span><span class="st">&quot;</span><span class="sc">${</span>system<span class="sc">}</span><span class="st">&quot;</span><span class="op">.</span>wrapperScript =</span>
<span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a>  <span class="kw">let</span></span>
<span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a>    <span class="va">shell</span> <span class="op">=</span> devShells<span class="op">.</span><span class="st">&quot;</span><span class="sc">${</span>system<span class="sc">}</span><span class="st">&quot;</span><span class="op">.</span>default<span class="op">;</span></span>
<span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a>  <span class="kw">in</span></span>
<span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a>    pkgs<span class="op">.</span>writeScriptBin <span class="st">&quot;cryptoisland-shell&quot;</span> <span class="st">&#39;&#39;</span></span>
<span id="cb6-6"><a href="#cb6-6" aria-hidden="true" tabindex="-1"></a><span class="st">      #!/usr/bin/env bash</span></span>
<span id="cb6-7"><a href="#cb6-7" aria-hidden="true" tabindex="-1"></a><span class="st">      export PATH=</span><span class="sc">${</span>pkgs<span class="op">.</span>lib<span class="op">.</span>makeBinPath shell<span class="op">.</span>buildInputs<span class="sc">}</span><span class="st">:$PATH</span></span>
<span id="cb6-8"><a href="#cb6-8" aria-hidden="true" tabindex="-1"></a><span class="st">      </span><span class="sc">${</span>shell<span class="op">.</span>shellHook<span class="sc">}</span></span>
<span id="cb6-9"><a href="#cb6-9" aria-hidden="true" tabindex="-1"></a><span class="st">      </span><span class="sc">${</span>pkgs<span class="op">.</span>bashInteractive<span class="sc">}</span><span class="st">/bin/bash $@</span></span>
<span id="cb6-10"><a href="#cb6-10" aria-hidden="true" tabindex="-1"></a><span class="st">    &#39;&#39;</span>;</span></code></pre></div>
<h2 id="devshell">devShell</h2>
<p>Set this up however you normally would.
It’s a regular flake-based dev shell.</p>
<p>The only gotcha is that if you add fields to the <code>mkShell</code> call,
you might also need to duplicate them in the wrapper script above.
That can often be avoided by moving them inside <code>shellHook</code>.</p>
<div class="sourceCode" id="cb7"><pre class="sourceCode nix"><code class="sourceCode nix"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a>devShells<span class="op">.</span><span class="st">&quot;</span><span class="sc">${</span>system<span class="sc">}</span><span class="st">&quot;</span><span class="op">.</span>default =</span>
<span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a>  <span class="kw">let</span></span>
<span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a>    <span class="va">myGhc</span> <span class="op">=</span> pkgs<span class="op">.</span>haskellPackages<span class="op">.</span>ghcWithPackages <span class="op">(</span><span class="va">ps</span><span class="op">:</span> <span class="kw">with</span> ps<span class="op">;</span> <span class="op">[</span></span>
<span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a>      bytestring</span>
<span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a>      hakyll</span>
<span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a>      hakyll-images</span>
<span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a>      hakyll-sass</span>
<span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a>      filepath</span>
<span id="cb7-9"><a href="#cb7-9" aria-hidden="true" tabindex="-1"></a>      pandoc</span>
<span id="cb7-10"><a href="#cb7-10" aria-hidden="true" tabindex="-1"></a>      MissingH</span>
<span id="cb7-11"><a href="#cb7-11" aria-hidden="true" tabindex="-1"></a>      hjsmin</span>
<span id="cb7-12"><a href="#cb7-12" aria-hidden="true" tabindex="-1"></a>      text</span>
<span id="cb7-13"><a href="#cb7-13" aria-hidden="true" tabindex="-1"></a>      language-javascript</span>
<span id="cb7-14"><a href="#cb7-14" aria-hidden="true" tabindex="-1"></a>      aeson</span>
<span id="cb7-15"><a href="#cb7-15" aria-hidden="true" tabindex="-1"></a>    <span class="op">]);</span></span>
<span id="cb7-16"><a href="#cb7-16" aria-hidden="true" tabindex="-1"></a>  <span class="kw">in</span></span>
<span id="cb7-17"><a href="#cb7-17" aria-hidden="true" tabindex="-1"></a>    pkgs<span class="op">.</span>mkShell <span class="op">{</span></span>
<span id="cb7-18"><a href="#cb7-18" aria-hidden="true" tabindex="-1"></a>      <span class="va">buildInputs</span> <span class="op">=</span> <span class="kw">with</span> pkgs<span class="op">;</span> <span class="op">[</span></span>
<span id="cb7-19"><a href="#cb7-19" aria-hidden="true" tabindex="-1"></a>        myGhc</span>
<span id="cb7-20"><a href="#cb7-20" aria-hidden="true" tabindex="-1"></a>        rsync</span>
<span id="cb7-21"><a href="#cb7-21" aria-hidden="true" tabindex="-1"></a>        graphviz</span>
<span id="cb7-22"><a href="#cb7-22" aria-hidden="true" tabindex="-1"></a>      <span class="op">];</span></span>
<span id="cb7-23"><a href="#cb7-23" aria-hidden="true" tabindex="-1"></a>      <span class="va">shellHook</span> <span class="op">=</span> <span class="st">&#39;&#39;</span></span>
<span id="cb7-24"><a href="#cb7-24" aria-hidden="true" tabindex="-1"></a><span class="st">        export LOCALE_ARCHIVE=&quot;</span><span class="sc">${</span>pkgs<span class="op">.</span>glibcLocales<span class="sc">}</span><span class="st">/lib/locale/locale-archive&quot;</span></span>
<span id="cb7-25"><a href="#cb7-25" aria-hidden="true" tabindex="-1"></a><span class="st">      &#39;&#39;</span><span class="op">;</span></span>
<span id="cb7-26"><a href="#cb7-26" aria-hidden="true" tabindex="-1"></a>    <span class="op">}</span>;</span></code></pre></div>
<p>You can still use this the normal flakes way too, via <code>nix develop</code>.
Just remember that if you have a different nixpkgs input in the repo vs your NixOS config,
it’ll rebuild the dependencies.</p>]]></summary>
</entry>

</feed>
