diff --git a/docs/STORAGE.md b/docs/STORAGE.md index db1e918..e25a78e 100644 --- a/docs/STORAGE.md +++ b/docs/STORAGE.md @@ -45,7 +45,19 @@ Before `configure-storage`: policy, website endpoint, CDN behavior, or public hostname to the inbox. 3. Configure a public HTTPS URL for the published bucket. Disable caching, or use a deliberately short TTL, for mutable `state/*`. Cache immutable - `blob/*` indefinitely. + `blob/*` indefinitely. The stale-pointer failure mode is why `state/*` must + never be cached: `state///head.json` is a mutable pointer at a + fixed key, and a CDN that serves a stale copy leaves participants seeing + "not your turn" and witnesses missing the closure window while nothing + reports an error. Verify freshness once per origin before a ceremony: + write a probe object under `state/`, read it through the public URL, + overwrite it, and confirm the very next read returns the new bytes. As + measured on 2026-08-20, both guided setups are already safe by + construction — CloudFront provisioned with the `CachingDisabled` managed + policy and Cloudflare's `r2.dev` origin do not cache at all — which also + means neither offloads `blob/*`; enabling `blob/*` caching is a pure + optimization, and any change to caching requires re-running the probe + check for `state/*`. 4. Create a coordinator runtime credential that can read, write, and delete in the published bucket and list, read, write, and delete in the inbox. Deletes are needed only for disposable preflight probes. @@ -144,6 +156,18 @@ For production, map an ordinary HTTPS hostname controlled by the coordinator, such as `https://ceremony.example.org`, to the published bucket. Cloudflare's generated `r2.dev` URL is rate-limited and intended for development. +The custom domain is an availability and longevity requirement, not an +integrity one: every published artifact is content-addressed and verified +against proof-tool signatures, so no URL can forge anything — it can only +fail to serve. What the coordinator-controlled hostname buys is immunity to +`r2.dev` rate limits during timed windows (each participant pulls the full +prefix and each auditor pulls the whole transcript), and a stable public +location for the transcript that survives bucket or provider moves for as +long as audits reference it. A rehearsal on `r2.dev` is fine. Note that a +custom domain routes through the zone's cache configuration while `r2.dev` +does not cache at all, so switching to one requires re-running the `state/*` +freshness check from the common prerequisites. + For a rehearsal, `configure-storage` refreshes the Wrangler OAuth token only for its one-time inbox privacy check; later grants and uploads do not require Wrangler. Its value is never written to the Machine 1 `.env`. For production diff --git a/scripts/three-machine-rehearsal/03-coordinator-issue-participant-grant.sh b/scripts/three-machine-rehearsal/03-coordinator-issue-participant-grant.sh index 2356396..640671f 100755 --- a/scripts/three-machine-rehearsal/03-coordinator-issue-participant-grant.sh +++ b/scripts/three-machine-rehearsal/03-coordinator-issue-participant-grant.sh @@ -25,8 +25,8 @@ trap clear_r2_parent_token EXIT --storage "$STORAGE_CONFIG" \ --role participant \ --identity "$participant_id" \ - --credential-ttl 1h \ - --minimum-upload-window 15m \ + --credential-ttl "${PARTICIPANT_GRANT_TTL:-1h}" \ + --minimum-upload-window "${PARTICIPANT_GRANT_MIN_UPLOAD_WINDOW:-15m}" \ --out "$out" chmod 0600 "$out" diff --git a/scripts/three-machine-rehearsal/11-coordinator-issue-evidence-grant.sh b/scripts/three-machine-rehearsal/11-coordinator-issue-evidence-grant.sh index 62f6aaf..24d18b1 100755 --- a/scripts/three-machine-rehearsal/11-coordinator-issue-evidence-grant.sh +++ b/scripts/three-machine-rehearsal/11-coordinator-issue-evidence-grant.sh @@ -29,8 +29,8 @@ trap clear_r2_parent_token EXIT --storage "$STORAGE_CONFIG" \ --role "$role" \ --identity "$identity" \ - --credential-ttl 1h \ - --minimum-upload-window 15m \ + --credential-ttl "${EVIDENCE_GRANT_TTL:-1h}" \ + --minimum-upload-window "${EVIDENCE_GRANT_MIN_UPLOAD_WINDOW:-15m}" \ --enrollment "$enrollment" \ --enrollment-signature "$enrollment_signature" \ --out "$out" diff --git a/scripts/three-machine-rehearsal/machine-1/.env.example b/scripts/three-machine-rehearsal/machine-1/.env.example index 7684def..b901d58 100644 --- a/scripts/three-machine-rehearsal/machine-1/.env.example +++ b/scripts/three-machine-rehearsal/machine-1/.env.example @@ -37,6 +37,17 @@ COORDINATOR_PROFILE=relay-ceremony # Machines 2 and 3 to observe the closure before the beacon exists. REHEARSAL_WITNESS_BUFFER_SECONDS=120 +# Grant lifetimes. The tiny rehearsal completes each turn in seconds, so the +# defaults are deliberately short-lived. A production-sized (K=21) turn can +# spend the better part of an hour on the contribution alone before its +# upload starts: raise these accordingly (for example 4h / 30m) when driving +# a production-sized ceremony with these scripts. R2 grants must stay within +# the 168h provider limit. +PARTICIPANT_GRANT_TTL=1h +PARTICIPANT_GRANT_MIN_UPLOAD_WINDOW=15m +EVIDENCE_GRANT_TTL=1h +EVIDENCE_GRANT_MIN_UPLOAD_WINDOW=15m + # AWS-only values. The wrapper reads the region and account ID through AWS CLI # and constructs GRANT_ROLE_ARN from GRANT_ROLE_NAME. # Recommended: set AWS_REGION explicitly to the region the buckets live in.