Site Updates: Dark Mode and About Page Pictures

May 27, 2024

A couple of site updates:

Dark Mode

Dark mode uses a prefers-color-scheme media query to determine the default theme, so it should do the right thing™ for people who have their system configured to prefer a dark color scheme. The theme can also be set explicitly using the “Switch Theme” icon on the right side of the menu bar.

There is a minor refresh bug and probably a few rough edges with older content, but I will get them sorted as time permits.


New dark theme active and "Switch Theme" menu icon highlighted.

New dark theme active and "Switch Theme" menu icon highlighted.

I strongly prefer dark mode and have wanted to add it here for years, so this change is a big victory for me.

About Page Pictures

There’s not much to say here; the About page now has a couple of pictures.

Adding the images in a way that I was satisfied with turned out to be a lot more effort than you might expect… This site is statically generated with Hugo, the theme is a heavily-customized version of Bulma, and blog posts and articles are written in Markdown.

The features that I wanted were:

  • A responsive layout containing a horizontal row of images on desktop which gracefully falls back to a vertical list of scaled thumbnmails on mobile.
  • Multiple image format support (in other words, the <picture> element).
  • Accessibility (title, alt, aria-label, <figcaption>, etc).
  • Ability to specifily all of the above in the article front matter.

In order to support everything above, I ended up copying my existing pe-figure shortcode and modifying it to support image “carousels” in articles.

In any case, it’s done now. Here is the result:

Image carousel at bottom of About page.

Image carousel at bottom of About page.

C11 FIPS 203 IPD v0.6

May 15, 2024

I just released v0.6 of fips203ipd.

fips203ipd is an embeddable, dependency-free, MIT-0 licensed, C11 implementation of the FIPS 203 initial public draft (IPD) with scalar, AVX-512, and Neon backends. The final version of FIPS 203 will become ML-KEM, NIST’s standarized post-quantum key encapsulation mechanism (KEM).

Git Repository, API Documentation, Original Announcement, pqc-forum Announcement

Changes in v0.6

  • Add Neon backend
  • Add MacOS support to test suite (thanks Rod!)
  • Add backend auto-detection, BACKEND command-line build parameter, and fips203ipd_backend() function
  • Add Raspberry Pi 5 (Cortex-A76) benchmarks
  • Add “Backends” documentation section with brief notes about each backend


Here are median cycle count as measured by the included bench tool for each parameter set, function, compiler, and backend from several of my systems.

For context, the results below are competitive with the eBATS results (kyber512, kyber768, kyber1024), although the comparison is inexact because the results were measured with different tools and because Kyber and ML-KEM differ slightly.

Lenovo ThinkPad X1 Carbon, 6th Gen (x86-64 i7-1185G7)

Median cycles by backend, Lenovo ThinkPad X1 Carbon, 6th Gen (i7-1185G7).

Median cycles by backend, Lenovo ThinkPad X1 Carbon, 6th Gen (i7-1185G7).

SetFunctionScalar (GCC)Scalar (Clang)AVX-512 (GCC)

Download CSV

Raspberry Pi 5 (ARM Cortex-A76)

Median cycles by backend, Raspberry Pi 5 (Cortex-A76).

Median cycles by backend, Raspberry Pi 5 (Cortex-A76).

SetFunctionScalar (GCC)Scalar (Clang)Neon (GCC)

Download CSV

Odroid N2L (ARM Cortex-A73)

Median cycles by backend, Odroid N2L (Cortex-A73).

Median cycles by backend, Odroid N2L (Cortex-A73).

SetFunctionScalar (GCC)Scalar (Clang)Neon (GCC)

Download CSV

Update (2024-05-16): Added cycle counts for scalar backend (clang and gcc), added bar charts, added downloadable CSVs. The CSVs and SVGs generated by the Python scripts in the scripts/bench-chart/ directory of the Git repository.

Solar Eclipse: Old Man Shakes Fist at Moon

April 8, 2024

Today’s solar eclipse took a healthy bite out of our solar energy production for the day. According to the details on, the partial eclipse in our area lasted from 2:04 pm until 4:32 pm (Eastern).

Those times line up perfectly with the dip shown on our dashboard:

Solar dashboard with eclipse time highlighted.

Solar dashboard with eclipse time highlighted.


Unfortunately we couldn’t find our solar eclipse glasses, so we had to fall back to ye olde pinhole method.

Update (2024-04-09): Several comments on this Ars Technica article also show dips in solar energy production.

New Releases of C11 SHA-3 and C11 FIPS 203 IPD

March 4, 2024

Two new releases:


Embedable, dependency-free, MIT-0 licensed, C11 implementation of all algorithms from FIPS 202, SP 800-185, and the draft KangarooTwelve and TurboSHAKE specification.

Git Repository, API Documentation, Original Announcement

Changes in v0.6

  • Improve speed of absorb, squeeze, and scalar Keccak permutation.
  • Refactor SHAKE128 and SHAKE256 functions.
  • Add CAVP tests in tests/cavp-tests.
  • Documentation improvements.


Embedable, dependency-free, MIT-0 licensed, C11 implementation of the FIPS 203 initial public draft (IPD). The final version of FIPS 203 will become ML-KEM, NIST’s standarized post-quantum key encapsulation mechanism (KEM).

Git Repository, API Documentation, Original Announcement

Changes in v0.3

  • Add AVX512 polynomial addition, subtraction, and multiplication.
  • Faster scalar Barrett reduction and scalar polynomial multiplication.
  • Upgrade to sha3 v0.6 for faster hash/XOF performance.
  • Add NIST draft ML-KEM test vectors to self tests and as examples/2-nist-tests/.
  • Embed hash/XOF functions, remove sha3.[hc].
  • Documentation improvements.

Solar: One Year

December 29, 2023

Total 2023 energy production and consumption.

Total 2023 energy production and consumption.

Last year we got rooftop solar. The picture above is our Enphase dashboard, which shows how the solar panels performed in 2023.

The blue bars indicate the energy produced by our solar panels and the orange bars indicate the energy consumed by our house, in megawatt-hours (MWh).

The relevant values are:

  • Consumed: Total energy consumed by our house.
  • Produced: Total energy generated by our solar panels.
  • Net Imported: Energy we are actually billed for.

Any excess energy that we produce is exported back to the grid and credited to our account (net metering), so our power bills during the spring and early summer look like this:

Our July power bill with monthly usage and amount due highlighted.

Our July power bill with monthly usage and amount due highlighted.

The sections of the bill highlighted in red show the following, respectively:

  1. Four months of zero net energy use.
  2. A bare minimum power bill.


Here’s a breakdown of the money we saved this year from solar:

DescriptionAmount ($)
Energy Produced (6.2 MWh, $0.14/kWh)$868.00
Fairfax County Solar Energy Equipment Tax Exemption$260.25


The expected lifetime of the solar panels is 20 years. We expect to recoup the initial installation cost in 11-13 years.

That’s longer than usual (most folks try to break even in 5-8 years), but we’re okay with it; our house is partially shaded and our panel placement is deliberately less than optimal for aesthetic reasons.

Firefox Redux

December 2, 2023

This weekend I installed Firefox Nightly from Mozilla’s APT repository and then switched my desktop browser from Chrome to Firefox. Thoughts so far…

Firefox Pros

  • uBlock Origin and other useful extensions will continue to work past June 2024.
  • Importing bookmarks, credentials, and history was seamless.
  • Tab switching and omnibar searches are faster than Chrome.
  • Firefox Sync and Firefox View are great; I prefer them to their Chrome equivalents.
  • Better interface customization.
  • Better bookmark management.
  • Better PDF reader.

Firefox Cons

  • No tab groups. There are several tab management extensions, but none are as elegant as tab groups. (Update: Tab Stash is a passable alternative for me).
  • Occasional screen tearing on complex pages.
  • Chrome has smoother scrolling.

I also worry about Mozilla as an organization; a substantial portion of their revenue comes from their contract with Google and they have made some questionable business decisions in the last couple of years.

Why Ditch Chrome?

Manifest V3 was the final straw; it is a thinly-veiled attempt to hobble ad blockers. Before that there was AMP, FLOC, Web Environment Integrity, Privacy Sandbox, and probably a dozen other anti-competitive and user-hostile changes I have forgotten about (see also: enshittification).

I switched from Firefox to Chrome a decade ago because of the minimalist interface and better performance. Neither reason applies today; the Chrome interface has become cluttered and Firefox performance has improved substantially.

In retrospect it was probably unwise for us to give an effective browser monopoly to an advertising company.

Other De-Googling Recommendations

  1. Search engine: Duck Duck Go. Fast, good results, spartan interface, useful shortcuts, doesn’t feed your search history to Google.
  2. Mobile: Firefox for Android. Fast, great interface, supports uBlock Origin. I’ve used it for years on my phone and tablet.

Update (2023-12-04): Added note about Tab Stash.

C11 FIPS 203 IPD

October 7, 2023

For fun and also to provide feedback during the draft phase, I created a C11 implementation of the FIPS 203 initial public draft (IPD).

FIPS 203 is a slightly modified version of Kyber, and will (eventually) become NIST’s standarized post-quantum key encapsulation mechanism (KEM).


  • Full implementation of all three parameter sets from the FIPS 203 initial public draft.
  • C11, no external dependencies (other than the standard library).
  • Constant-time Barrett reduction. Not vulnerable to KyberSlash.
  • Test suite w/ common sanitizers enabled (make test).
  • Doxygen-friendly API documentation (fips203ipd.h). Also available online here.
  • Short example application (examples/0-hello-kem/).
  • Independent implementation. Not based on other libraries.

Git Repository, API Documentation

Note: This is an initial release based on the draft standard with no real optimization; it is probably slower than other implementations.

Another Note: Worth reading before relying on any Kyber implementation: 2020.10.03: The inability to count correctly, by Dan Bernstein (djb).


Below is the source code and output of a minimal C11 example application which demonstrates the following:

  1. Alice generates a random KEM512 encapsulation/decapsulation key pair.
  2. Alice sends the encapsulation key to Bob.
  3. Bob uses the encapsulation key sent by Alice to encapsulate a random shared secret as ciphertext.
  4. Bob sends the ciphertext to Alice.
  5. Alice uses the decapsulation key to decapsulate the shared secret from the ciphertext sent by Bob.
  6. Application verifies that the shared secrets from steps #3 and #5 match.

This example is also included in the git repository as examples/0-hello-kem/.

Example Source Code

// hello.c: minimal example of a two parties "alice" and "bob"
// generating a shared secret with KEM512.
// Build by typing `make` and run by typing `./hello`.

#include <stdio.h> // fputs()
#include <string.h> // memcmp()
#include "hex.h" // hex_write()
#include "rand-bytes.h" // rand_bytes()
#include "fips203ipd.h" // fips203ipd_*()

int main(void) {
  // alice: generate keypair
  uint8_t ek[FIPS203IPD_KEM512_EK_SIZE] = { 0 }; // encapsulation key
  uint8_t dk[FIPS203IPD_KEM512_DK_SIZE] = { 0 }; // decapsulation key
    // alice: get 64 random bytes for keygen()
    uint8_t keygen_seed[64] = { 0 };
    rand_bytes(keygen_seed, sizeof(keygen_seed));

    fputs("alice: keygen random (64 bytes) = ", stdout);
    hex_write(stdout, keygen_seed, sizeof(keygen_seed));
    fputs("\n", stdout);

    // alice: generate encapsulation/decapsulation key pair
    fips203ipd_kem512_keygen(ek, dk, keygen_seed);

  fputs("alice: generated encapsulation key `ek` and decapsulation key `dk`:\n", stdout);
  printf("alice: ek (%d bytes) = ", FIPS203IPD_KEM512_EK_SIZE);
  hex_write(stdout, ek, sizeof(ek));
  printf("\nalice: dk (%d bytes) = ", FIPS203IPD_KEM512_DK_SIZE);
  hex_write(stdout, dk, sizeof(dk));
  fputs("\n", stdout);

  // alice send `ek` to bob
  fputs("alice: sending encapsulation key `ek` to bob\n\n", stdout);

  // bob: generate shared secret and ciphertext
  uint8_t b_key[32] = { 0 }; // shared secret
  uint8_t ct[FIPS203IPD_KEM512_CT_SIZE] = { 0 }; // ciphertext
    // bob: get 32 random bytes for encaps()
    uint8_t encaps_seed[32] = { 0 };
    rand_bytes(encaps_seed, sizeof(encaps_seed));

    fputs("bob: encaps random (32 bytes) = ", stdout);
    hex_write(stdout, encaps_seed, sizeof(encaps_seed));
    fputs("\n", stdout);

    // bob:
    // 1. get encapsulation key `ek` from alice.
    // 2. generate random shared secret.
    // 3. use `ek` from step #1 to encapsulate the shared secret from step #2.
    // 3. store the shared secret in `b_key`.
    // 4. store the encapsulated shared secret (ciphertext) in `ct`.
    fips203ipd_kem512_encaps(b_key, ct, ek, encaps_seed);

  fputs("bob: generated secret `b_key` and ciphertext `ct`:\nbob: b_key (32 bytes) = ", stdout);
  hex_write(stdout, b_key, sizeof(b_key));
  printf("\nbob: ct (%d bytes) = ", FIPS203IPD_KEM512_CT_SIZE);
  hex_write(stdout, ct, sizeof(ct));
  fputs("\n", stdout);

  // bob sends ciphertext `ct` to alice
  fputs("bob: sending ciphertext `ct` to alice\n\n", stdout);

  // alice: decapsulate shared secret

  // alice:
  // 1. get ciphertext `ct` from bob.
  // 2. use decapsultion key `dk` to decapsulate shared secret from `ct`.
  // 2. store shared secret in `a_key`.
  uint8_t a_key[32] = { 0 };
  fips203ipd_kem512_decaps(a_key, ct, dk);

  fputs("alice: used `dk` to decapsulate secret from `ct` into `a_key`:\nalice: a_key (32 bytes) = ", stdout);
  hex_write(stdout, a_key, sizeof(a_key));
  fputs("\n\n", stdout);

  // check result
  // (note: example only; memcmp() is not constant-time)
  if (!memcmp(a_key, b_key, sizeof(a_key))) {
    // success: alice and bob have the same shared secret
    fputs("SUCCESS! alice secret `a_key` and bob secret `b_key` match.\n", stdout);
    return 0;
  } else {
    // failure: alice and bob do not have the same shared secret
    fputs("FAILURE! alice secret `a_key` and bob secret `b_key` do not match.\n", stdout);
    return -1;

Example Output

Output of ./hello with longer lines truncated for brevity:

> ./hello
alice: keygen random (64 bytes) = d656012a9eb09aa50e77a205188f0156e98276a584dcc11c2dfef0c06003ca38b233fab93e9f8dd5adec32278c8d091190112285b7389510bd610ec7b23376b2
alice: generated encapsulation key `ek` and decapsulation key `dk`:
alice: ek (800 bytes) = af3b0497f6 ... (omitted) ... 31f0f62cbd
alice: dk (1632 bytes) = e598a49eb0 ... (omitted) ... c06003ca38
alice: sending encapsulation key `ek` to bob

bob: encaps random (32 bytes) = 0db6c39742ba8cb8d9a1c437d62fed4c7fa04e944a47a73a94426dd3c33e6213
bob: generated secret `b_key` and ciphertext `ct`:
bob: b_key (32 bytes) = 32c9eb490db7e8500d9b209d78a9367fd73a967d8d58edff8655273c8d4ce8d5
bob: ct (768 bytes) = bd39ae1157 ... (omitted) ... 9b5751fc34
bob: sending ciphertext `ct` to alice

alice: used `dk` to decapsulate secret from `ct` into `a_key`:
alice: a_key (32 bytes) = 32c9eb490db7e8500d9b209d78a9367fd73a967d8d58edff8655273c8d4ce8d5

SUCCESS! alice secret `a_key` and bob secret `b_key` match.


Update (2023-10-10): Fixed typos, added rationale to intro, and added a brief explanation to the example section.

Update (2023-10-19): Released v0.2 with documentation improvements, speed improvements, a new example, and online API documentation.

Update (2024-02-14): Added Barrett reduction and independent implementation to feature list. Minor wording fixes.

Update (2024-03-04): Released v0.3. Added AVX-512 polynomial arithmetic, speed improvements, the NIST draft ML-KEM test vectors, and documentation updates.

Update (2024-04-08): Released v0.4. Added AVX-512 NTT and inverse NTT, add “Benchmarks” and “AVX-512 Backend” sections to

Update (2024-04-28): Released v0.5. Much faster (AVX-512: ~27% reduction in median CPU cycles, scalar: ~13% reduction in median CPU cycles), code cleanup, internal documentation improvements.

Update (2024-05-15): Released v0.6. Added Neon backend, backend auto-detection, test suite MacOS support, Raspberry Pi 5 benchmarks, documentation improvements.

C11 SHA-3

September 5, 2023

This weekend I put together a C11 implementation of the following SHA-3 algorithms from FIPS 202, SP 800-185, and the draft KangarooTwelve and TurboSHAKE specification:

  • SHA3-224, SHA3-256, SHA3-384, and SHA3-512
  • SHAKE128 and SHAKE256
  • HMAC-SHA3-224, HMAC-SHA3-256, HMAC-SHA3-384, and HMAC-SHA3-512
  • cSHAKE128, cSHAKE128-XOF, cSHAKE256, and cSHAKE256-XOF
  • KMAC128, KMAC128-XOF, KMAC256, and KMAC256-XOF
  • TupleHash128, TupleHash128-XOF, TupleHash256, and TupleHash256-XOF
  • ParallelHash128, ParallelHash128-XOF, ParallelHash256, and ParallelHash256-XOF
  • TurboSHAKE128 and TurboSHAKE256
  • KangarooTwelve

Git Repository, API Documentation



// example.c: print hex-encode sha3-256 hash of each command-line argument
// build:
//   cc -o example -std=c11 -O3 -W -Wall -Wextra -Werror -pedantic -march=native -mtune=native example.c sha3.c
#include <stdint.h> // uint8_t
#include <stdio.h> // printf()
#include <string.h> // strlen()
#include "sha3.h" // sha3_256()

// print hex-encoded buffer to stdout.
static void print_hex(const uint8_t * const buf, const size_t len) {
  for (size_t i = 0; i < len; i++) {
    printf("%02x", buf[i]);

int main(int argc, char *argv[]) {
  // loop over command-line arguments
  for (int i = 1; i < argc; i++) {
    // hash argument
    uint8_t buf[32] = { 0 };
    sha3_256((uint8_t*) argv[i], strlen(argv[i]), buf);

    // print argument and hex-encoded hash
    printf("\"%s\",", argv[i]);
    print_hex(buf, sizeof(buf));
    fputs("\n", stdout);

  // return success
  return 0;



> ./example asdf foo bar


Update (2023-09-07): Released v0.2 with TurboSHAKE128, TurboSHAKE256, KangarooTwelve, and an examples/ directory.

Update (2023-09-18): Released v0.3 with AVX-512 support (~3x faster) and miscellaneous small fixes.

Update (2023-10-15): Released v0.4 with lots of documentation improvements.

Update (2023-10-19): Released v0.5 with more documentation improvements and additional examples. added online API documentation.

Update (2024-03-02): Released v0.6 with speed improvements, simplified SHAKE API, and documentation updates. Added CAVP test vectors.

End of May Miscellany

June 2, 2023

Bookworm Upgrades

I upgraded several more systems to Bookworm, including a Raspberry Pi 4 Model B 4GB and a Raspberry Pi Zero W.

All of the upgrades have gone smoothly except for the Pi Zero W, which failed with “Illegal instruction” errors during dist-upgrade and left the system unbootable in two separate upgrade attempts.

I don’t know if this is a pre-release issue with Bookworm or if this particular Pi Zero W is starting to fail. In either case, be sure to back up your SD card before trying to upgrade a Pi Zero Ws to Bookworm.

PNG Compressors

I tried the following PNG compressors on a set of several dozen screenshots:

  • pngcrush: Lossless PNG compression command-line tool.
  • optipng: Lossless multi-format image compression command-line tool.
  • pngquant: Lossy PNG compression command-line tool and library.

pngquant produced the smallest output images. The results were also visually indistinguishable from the source images, at least for my use case. Of the two lossless encoders, optipng produced marginally smaller images than pngcrush.

(The bitmap images on this site are served as lossless WebPs with PNG as a fallback, courtesy of the <picture> element).

Domain Registrar

I’ve been considering switching to another domain registrar for a few years. The recent .zip TLD tizzy seemed like a perfect opportunity to register a new domain through a different registrar. I’d heard good things about Namecheap, so I created an account and registered my shiny new .zip domain:

Thoughts on Namecheap so far:

  • Good prices.
  • Fast and simple interface. Account setup and domain registration only took a few minutes.
  • 2FA prominently located and easy to enable.
  • They haven’t sold, lost, or spammed my email address (yet).
  • DNS propagation only took 2 hours.

My next step will be to migrate several secondary domains from GoDaddy to Namecheap.

Update (2023-06-13): Last night I followed these instructions to migrate 7 secondary domains. This afternoon I migrated the remaining 6 domains.

If you disable locking, disable domain protection, and manually approve pending transfers, the entire process takes about an hour.

Solar: A Great Month

May 2, 2023

Last month our solar panels generated more energy than we consumed for the entire month. Here’s the Enphase dashboard for April:

April energy production and consumption.

April energy production and consumption.


Here is our latest power bill:

Power bill from March 21st to April 24th.

Power bill from March 21st to April 24th.

(Note: The date ranges in the two pictures overlap by 21 days but don’t match precisely, because our billing period is not quite monthly).

The highlighted sections of the bill above show the following:

  • The total was $7.14
  • We have an 18 kWh credit (e.g. net metering)

The kicker? Our car is electric; the bill includes the electricity for the house and the car.

Archived Posts...
© 1998-2024 Paul Duncan