From 28169a648fb82300aa124844a64225deff4b1095 Mon Sep 17 00:00:00 2001 From: Paul Hitt Date: Fri, 4 Sep 2026 20:29:01 -0400 Subject: [PATCH] PARITY: post-quantum is shipped on both clients, not missing on either MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both rows said ⚪/⚠️ and the note under them said "neither client does post-quantum anything today". That was wrong in the understating direction — the same direction relay#45 warns about, and the same one that had the comparison page scoring Signal unfairly in our favour. - **Android** uses BouncyCastle's ML-KEM (FIPS 203), shipped in the engine. The old note's "no ML-KEM at any API level" was about the PLATFORM, which is true and is why the engine carries its own — not a reason it has none. - **iOS** uses CryptoKit `MLKEM768`, unconditional since `project.yml` moved the deployment target to 26.0. The runtime gate that made those files inert is gone, and `PQKEM.swift` says so at the top. ⚠️ Corrected by a run rather than by reading the source, which is the rule this file keeps to. `test/interop-swift-kotlin.mjs` establishes it end to end in both directions: "Android picks v3 for an iPhone peer", "the first Android envelope carries a PQ ratchet step", "the iPhone agrees the session is v3", "a v3 session survives being archived and restored mid-conversation". ⚠️ The honest limit is unchanged and kept: it counts only once BOTH ends have it, so today that means two Android phones — or an Android phone and an iPhone running a build that has not shipped. Found while correcting relay#11, where I went in expecting to find our own cells overstated and found the opposite. Co-Authored-By: Claude Opus 5 --- PARITY.md | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/PARITY.md b/PARITY.md index 6cc9542..e4873c5 100644 --- a/PARITY.md +++ b/PARITY.md @@ -45,17 +45,35 @@ everything above the protocol. | Device identity + prekey rotation | ✅ | ✅ | retired prekeys retained past the relay window | | Vault (encryption at rest) | ✅ | ✅ | Android Keystore, AES-GCM, record name bound into the AAD | -| ML-KEM-768 (PQ agreement) | ⚠️ | ⚪ | see below | -| ML-DSA-65 (PQ identity) | ⚠️ | ⚪ | see below | +| ML-KEM-768 (PQ agreement) | ✅ | ✅ | cross-engine verified — see below | +| ML-DSA-65 (PQ identity) | ✅ | ✅ | see below | -⚠️ **The post-quantum gap is not what it looks like.** iOS has `PQKEM.swift` and -`PQSign.swift`, and they are inert: CryptoKit exposes ML-KEM only on iOS 26 and -the app targets iOS 16, so no shipped iOS build does post-quantum anything. -Android has no platform ML-KEM at any API level. So the two clients are -*currently equal in effect* and the honest parity target is v1/v2 negotiation -that degrades cleanly — which the Android engine already does, and the interop -harness asserts. Closing this properly is a separate decision (implement, or -take a vetted library) and it should be made once, for both clients. +🔴 **These two rows said ⚪ and ⚠️, and both were wrong — understating, which +is the same direction relay#45 warns about and the same one that had the +comparison page scoring a competitor unfairly.** The note that used to sit here +said iOS's PQ files were inert because CryptoKit exposes ML-KEM only on iOS 26 +while the app targeted 16, that Android had no ML-KEM at any API level, and so +"neither client does post-quantum anything today". None of that is true now, and +the middle claim was never quite the point. + +- **Android** uses BouncyCastle's `MLKEMExtractor` / `MLKEMGenerator` — FIPS 203, + a real implementation shipped in the engine. There is no *platform* ML-KEM on + Android at any API level, which is what the old note was reaching for, and it + is why the engine carries its own rather than a reason it has none. +- **iOS** uses CryptoKit's `MLKEM768`, and it is **unconditional**: + `project.yml` sets the deployment target to 26.0, so the runtime gate that + made those files inert is gone. `PQKEM.swift` says so at the top. + +⚠️ Corrected by a run, not by reading the source. The cross-engine harness +(`test/interop-swift-kotlin.mjs`) establishes it end to end: *"Android picks v3 +for an iPhone peer"*, *"the first Android envelope carries a PQ ratchet step"*, +*"the iPhone agrees the session is v3"*, and *"a v3 session survives being +archived and restored mid-conversation"* — with the same four in the other +direction. + +⚠️ The honest limit, which has not changed: post-quantum protection counts only +once **both** ends have it, so today that means two Android phones, or an +Android phone and an iPhone running a build that has not shipped yet. --- -- 2.54.0