diff --git a/core/archipelago/src/trust/anchor.rs b/core/archipelago/src/trust/anchor.rs index 8498532e..30bfe0a6 100644 --- a/core/archipelago/src/trust/anchor.rs +++ b/core/archipelago/src/trust/anchor.rs @@ -16,13 +16,28 @@ use ed25519_dalek::VerifyingKey; /// Hex of the pinned Ed25519 release-root public key (32 bytes / 64 hex chars). /// -/// Pinned 2026-07-02 from the release-root signing ceremony -/// (signer did:key:z6MkkidEnEpo6qHMCNSZoNKWtvQvxq3whnaME9wGgEFhq7ur). The +/// ROTATED 2026-08-04 to did:key:z6Mkfu5LT8d4DjETtrkATvHh9Dvcbnr7zBCUwfau8Sw7DLWT. +/// +/// The previous root (z6Mkkid…q7ur, pinned 2026-07-02) was exposed in a chat +/// transcript and is treated as compromised. +/// +/// Rotation is ORDERING-CRITICAL. Nodes pin the OLD key, so the release that +/// carries this change must itself be signed with the OLD key — that is the +/// only signature a node running the previous binary will accept. Only the +/// release AFTER it may be signed with the new key. Signing the rotation +/// release with the new key makes every node reject it and ends OTA +/// fleet-wide, recoverable only by touching each node by hand. +/// +/// Verified before pinning: this hex and the did:key above are the same +/// keypair (the did:key encodes exactly these 32 bytes), checked with a +/// decoder round-tripped against the previous known-good pair. An earlier +/// candidate hex was rejected because it did not match the stated DID. +/// The /// corresponding mnemonic is held offline by the publisher — see /// `docs/workstream-b-signing-runbook.md`. Regenerate/verify with: /// `RELEASE_MASTER_MNEMONIC=… archipelago ceremony pubkey`. pub const RELEASE_ROOT_PUBKEY_HEX: Option<&str> = - Some("5d15cbee8a108f7dd288c02d29a1d9d71f198acc99186aad8008b4f28d469951"); + Some("1578adccf137024159dd936f44a56e8869ac7775785962f7e92e2faf2c034418"); const ENV_OVERRIDE: &str = "ARCHY_RELEASE_ROOT_PUBKEY"; diff --git a/scripts/create-release.sh b/scripts/create-release.sh index c35ddb11..d836ff9b 100755 --- a/scripts/create-release.sh +++ b/scripts/create-release.sh @@ -240,7 +240,7 @@ install -m 0644 "$FRONTEND_ARCHIVE" "$VERSION_DIR/archipelago-frontend-${VERSION # warning and falls through — and the commit then happened anyway. A release # commit carrying a manifest no node will accept has no valid use, so refuse # to create one rather than leave a tag that has to be re-cut. -EXPECTED_DID="did:key:z6MkkidEnEpo6qHMCNSZoNKWtvQvxq3whnaME9wGgEFhq7ur" +EXPECTED_DID="did:key:z6Mkfu5LT8d4DjETtrkATvHh9Dvcbnr7zBCUwfau8Sw7DLWT" if ! grep -q '"signature":' "$PROJECT_ROOT/releases/manifest.json" \ || ! grep -q "\"signed_by\": \"$EXPECTED_DID\"" "$PROJECT_ROOT/releases/manifest.json"; then echo "" >&2 diff --git a/scripts/publish-release-assets.sh b/scripts/publish-release-assets.sh index 1650d0a9..04f67c68 100755 --- a/scripts/publish-release-assets.sh +++ b/scripts/publish-release-assets.sh @@ -29,7 +29,7 @@ fail() { echo "Error: $*" >&2; exit 1; } # with the pinned release-root anchor refuse to auto-apply unsigned manifests, # and enforcement will tighten to hard-reject — an unsigned publish would # strand them. Grep proves presence; ceremony verify proves the crypto. -EXPECTED_DID="did:key:z6MkkidEnEpo6qHMCNSZoNKWtvQvxq3whnaME9wGgEFhq7ur" +EXPECTED_DID="did:key:z6Mkfu5LT8d4DjETtrkATvHh9Dvcbnr7zBCUwfau8Sw7DLWT" grep -q '"signature":' "$PROJECT_ROOT/releases/manifest.json" \ && grep -q "\"signed_by\": \"$EXPECTED_DID\"" "$PROJECT_ROOT/releases/manifest.json" \ || fail "releases/manifest.json is not signed by the release root — run: bash scripts/sign-manifest.sh" diff --git a/scripts/sign-catalog.sh b/scripts/sign-catalog.sh index c9ea3727..71ea7021 100755 --- a/scripts/sign-catalog.sh +++ b/scripts/sign-catalog.sh @@ -11,7 +11,7 @@ set -euo pipefail REPO="/home/archipelago/Projects/archy" CATALOG="$REPO/releases/app-catalog.json" -EXPECTED_DID="did:key:z6MkkidEnEpo6qHMCNSZoNKWtvQvxq3whnaME9wGgEFhq7ur" +EXPECTED_DID="did:key:z6Mkfu5LT8d4DjETtrkATvHh9Dvcbnr7zBCUwfau8Sw7DLWT" # Use ONLY the prebuilt signer. If it isn't ready, stop cleanly — never compile # here (compiling caused the earlier hangs). Claude builds it in the background.