keys.md: every path that trusts K1 (app, jobs, Windows inputs, rig installer, HiveOS, wallet) with its state; the rig installer's two-key form; HiveOS README says the archive is unsigned and names the sha256 check (consequences C21)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-05 20:42:43 +00:00
parent 9062ed3e0c
commit 7a412bd53e
2 changed files with 25 additions and 0 deletions

View file

@ -104,6 +104,27 @@ next cut the Counter ASIC 2.0 coordinator assembles, not into 0.3.10 or 0.3.11).
| 4 | Ship it in the next cut (the plan said 0.3.9; 0.3.9 and 0.3.10 went out without it, so it is 0.3.12 or whatever the coordinator numbers it), signed with K1, so every 0.3.5+ machine takes it on its hourly check. The publisher keeps signing with K1; K2 stays in its image. The engine logs a third header line, `ota keys: trusted <fp8> [<fp8>]; revoked none|<fp8>...`, and `tools/logs.mjs --rotation` has the `ota_keys` column (K1 and K2 named from the two `.pub` files; "REVOKED" when a machine reports one) and the line "N machine(s) embed K2, M embed K1 only, P log no ota keys line" | code done; the ship is the coordinator's |
| 5 | When every machine reports a build with K2: K1 lost = `tools/keys/with-k2.sh <image> -- packaging/ota/publish-manifest.sh ...` (the image attached read-only for the minutes of the publish, `IGNEUM_OTA_KEY_FILE`/`IGNEUM_OTA_PUB_FILE` set; `publish-jobs.sh` and `push-inputs.sh` read the same two); K1 leaked = the same with `--revoke 8f186e37b48cfddf52d2b37478c562d78a74236fc87f0a675393ed6a35baac4e` (K1's fingerprint). Known limit, accepted with the plan: the two keys are peers, so whoever holds a leaked K1 can sign a manifest that revokes K2 just as well; whichever manifest a machine fetches first wins, and the mitigation table below (take the files off the folder first) is what makes ours arrive first | when the fleet has moved |
Every path that trusts K1, and where each stands (consequences review C21, 5 October 2026). "K2 + revocation" means
the path verifies with the `[K1, K2]` list and honours `revoked_keys` as `manifest.rs check()` does.
| Path | What it verifies | Key(s) today | State | Who |
|---|---|---|---|---|
| the app (Mac, Windows), `ota.rs fetch_manifest` | the update manifest | `OTA_PUBLIC_KEYS` minus `updates/revoked.json` | K2 + revocation, this branch | done |
| the app's jobs runner, `jobrun.rs fetch_jobs` | `igneum-jobs.json` / `.signed.json` (`kind: run` executes on every machine) | the same trusted list, read at every poll | K2 + revocation, this branch (the jobs file itself carries no `revoked_keys`; the manifest does) | done |
| the Windows build, `windows.yml:172` (`igneum-ota-sign verify-inputs embedded`) | `payload-inputs.json` on GitHub's runner | `OTA_PUBLIC_KEYS` (no revocation record on a runner) | K2, this branch; a revoked key still verifies in CI, which only gates what the Mac then signs | done |
| the rig installer and its hourly updater, branch `rig-install` (`packaging/linux/bin/igneum-rig-lib.sh:16,79,92`, `install-rig.sh:163-174`, `igneum-update.sh:2-5`) | the update manifest: the consensus override and the Linux package | `OTA_PUBLIC_KEY_HEX` alone, openssl or python3-cryptography; no revocation | OPEN: K1 lost = every rig refuses a K2-signed manifest, takes no override and is isolated at the next height switch; K1 leaked = it keeps signing for rigs. Needs the same list and rule (the form sent to the rig agent, below) | rig-install |
| the HiveOS package, `packaging/hive` | nothing: the override reaches a Hive rig only by a package republish; nothing checks who published the archive | none | OPEN: unsigned; the README now says so and names the check (the archive's sha256 from `make-hive-package.sh` against the file the Flight Sheet URL serves) | packaging/hive |
| the wallet, branch `wallet-v1` (`app/igneum-common/src/manifest.rs`, `updater.rs:729`) | the wallet manifest | `OTA_PUBLIC_KEY_HEX` alone; no revocation | OPEN: the follow-up at the end of this section | wallet-v1 |
The form the rig installer takes, so one pair of files serves the app and the rigs: `OTA_PUBLIC_KEYS` as two hex
constants (`b3c9c5bd144e9d246dc0edf897387d4f3f7ca494cd47457123d1c2f892cabddd` = K1, and K2's 64 hex once
`keygen-k2.sh` has made it; empty = skipped), the signature tried against each in turn, the key that verified
remembered; the fingerprint = sha256 of the 32 RAW key bytes as hex (`printf '%s' "$hex" | xxd -r -p | sha256sum`),
K1 = `8f186e37b48cfddf52d2b37478c562d78a74236fc87f0a675393ed6a35baac4e`; a verified manifest's `revoked_keys`
(an array of such fingerprints) is recorded in `/var/lib/igneum/updates/revoked.json` except the signer's own
fingerprint (a key never revokes itself), and a recorded key is never tried again, before the openssl or python
verify. Same words in the log: "manifest signature is by a revoked key (sha256:<8 hex>)".
What the project lead runs (step 1), in the main checkout once this branch is merged, in this order:
```

View file

@ -6,6 +6,10 @@ the two GPU workers (`igneum-worker-cuda` for NVIDIA, `igneum-worker-opencl` for
scripts were self-tested with stub binaries (`selftest.sh`) and the binaries were cross-compiled on a Mac; the first real
run on a Hive rig is still to come. Report what breaks.
The archive is UNSIGNED (unlike the app's update manifest, docs/security/keys.md section 4): before a Flight Sheet
points at a new `igneum-hive-<version>.tar.gz`, check the file the URL serves against the sha256 that
`make-hive-package.sh` printed for that build (`curl -fsSL <url> | sha256sum`), and republish only from this Mac.
## Flight Sheet
| Field | Value |