Commit Graph
2 Commits
Author SHA1 Message Date
Paul HittandClaude Opus 5 bff7a4bbdd Kotlin: HKDF-SHA256 and AES-256-GCM, against external vectors
Checked against RFC 5869 and NIST CAVP rather than against our JavaScript
engine. An implementation validated only against our other implementation
inherits our misunderstandings, and finding where the first two quietly
agreed on something the spec does not say is the entire reason for writing
a third.

Three things pinned that a naive version gets wrong:

- An empty HKDF salt means HashLen zeros, not "no HMAC key". SecretKeySpec
  throws on a zero-LENGTH key, so the naive version crashes on exactly the
  case the app uses -- §12 derives its per-context secrets with an empty
  salt.
- 255 blocks is a hard ceiling. The counter is one byte; a naive loop wraps
  at 256 and silently returns key material it already returned.
- The AEAD box is nonce || ciphertext || tag. CryptoKit's `combined` emits
  that layout and the JS engine assembles it by hand to match, so a box
  built any other way decrypts to garbage on the other engines rather than
  failing loudly.

HKDF is hand-written on Mac rather than taken from a provider: Android's
providers disagree about whether HKDF exists at all.

Kotlin engine: 9 tests -> 27.

⚠️ Still JVM-only. These prove nothing about Android, where crypto goes
through Conscrypt rather than the JVM's JCA, and nothing here has ever been
compiled against the Android SDK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 11:22:31 -04:00
Paul HittandClaude Opus 5 06b97ca69b Start Android with the engine, held to the published vectors
Android does not exist, and the first thing it needs is not a screen. It
needs the protocol implemented a third time — which is only worth
anything if the third implementation is checked against the spec and the
published vectors rather than against a reading of our own Swift.

So `android/engine` is a plain Kotlin/JVM library with no Android in it:
no SDK, no emulator, no device, and therefore gateable in CI on Linux
like everything else. It reads
engine/test/vectors/protocol-v1.json from its one location rather than
carrying a copy, because a vector transcribed into the test that checks
it is a vector that agrees with itself.

Covered so far: safety numbers (§7) and key transparency verification
(§19) — RFC 6962 leaf and node hashing, the unbalanced split, and
bottom-up audit paths. Nine tests, all green against the same file the
JavaScript and Swift engines answer to.

The §19 tests are the ones worth having. A top-down reading of a
bottom-up audit path yields the correct root for every left-spine index,
so it passes any suite that only checks index 0 — the vectors pin a
right-descending index, and the tests assert both that the published
path verifies and that the same path reversed does not.

android/README.md lists what is missing — X25519, X3DH, the ratchet,
sealed sender, both post-quantum halves, the relay client, the app — as
a table rather than as an omission. Android stays honestly marked "not
started" on the site until that list is shorter.

Bytecode targets 17 without demanding a JDK 17 be installed: a
jvmToolchain(17) fails on any newer JDK, which is a poor trade for a
library with no JDK 17-specific behaviour.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 13:00:13 -04:00