Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions COORDINATOR_RUNBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,13 @@ head. It downloads into a fresh review directory and invokes
the new head. Inbox submissions remain available for review or provider
lifecycle cleanup.

By default Relay records `accepted_at` from the coordinator clock after the
candidate download, preserving subsecond precision so it is strictly later
than the participant's signed `destroyed_at`. Keep participant and coordinator
clocks synchronized. Use `--accepted-at RFC3339` only for a controlled replay;
the explicit value must still be strictly later than the signed erasure and
the preceding accepted record.

Repeat for every participant and phase.

## 5. Publish lifecycle changes
Expand Down
18 changes: 12 additions & 6 deletions cmd/relay/coordinator_workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func runAcceptCandidate(args []string) error {
set.StringVar(&root, "root", "", "coordinator transcript root")
set.StringVar(&candidateDir, "candidate-dir", "", "fresh local directory for the downloaded candidate")
set.StringVar(&coordinatorSigningKey, "coordinator-signing-key", "", "coordinator Ed25519 private key")
set.StringVar(&acceptedAt, "accepted-at", "", "acceptance timestamp (defaults to now)")
set.StringVar(&acceptedAt, "accepted-at", "", "acceptance timestamp (defaults to the current time after candidate download)")
set.StringVar(&phase1Seal, "phase1-seal", "", "phase 2: sealed phase-1 record")
set.StringVar(&phase1SealSignature, "phase1-seal-signature", "", "phase 2: phase-1 seal signature")
set.BoolVar(&verify, "verify-publish", false, "confirm every published object after advancing the head")
Expand All @@ -81,11 +81,10 @@ func runAcceptCandidate(args []string) error {
if storagePath == "" || candidateKey == "" || root == "" || candidateDir == "" || coordinatorSigningKey == "" {
return errors.New("--storage, --candidate-key, --root, --candidate-dir and --coordinator-signing-key are required")
}
if acceptedAt == "" {
acceptedAt = time.Now().UTC().Format(time.RFC3339)
}
if _, err := time.Parse(time.RFC3339, acceptedAt); err != nil {
return errors.New("--accepted-at must be RFC3339")
if acceptedAt != "" {
if _, err := time.Parse(time.RFC3339, acceptedAt); err != nil {
return errors.New("--accepted-at must be RFC3339")
}
}
if !safeObjectKey(candidateKey) || !strings.HasSuffix(candidateKey, "/manifest.json") {
return errors.New("--candidate-key must be a safe manifest object key")
Expand Down Expand Up @@ -161,6 +160,9 @@ func runAcceptCandidate(args []string) error {
}); err != nil {
return err
}
if acceptedAt == "" {
acceptedAt = defaultAcceptanceTimestamp(time.Now())
}
command := []string{manifest.Phase, "verify", "--ceremony", config.CeremonyPath,
"--ceremony-signature", config.CeremonySignature, "--coordinator-public-key-file", config.CoordinatorPublicKey,
"--transcript-dir", root, "--chain", pos.chainPath, "--chain-signature", pos.chain.ChainSignaturePath,
Expand Down Expand Up @@ -198,6 +200,10 @@ func runAcceptCandidate(args []string) error {
return nil
}

func defaultAcceptanceTimestamp(now time.Time) string {
return now.UTC().Format(time.RFC3339Nano)
}

func runEvidenceInbox(args []string) error {
set := flag.NewFlagSet("coordinator evidence", flag.ContinueOnError)
var storagePath, role string
Expand Down
23 changes: 23 additions & 0 deletions cmd/relay/coordinator_workflow_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package main

import (
"testing"
"time"
)

func TestDefaultAcceptanceTimestampPreservesSubsecondOrdering(t *testing.T) {
destroyedAt := time.Date(2026, time.August, 20, 12, 0, 0, 0, time.UTC)
acceptedAt := destroyedAt.Add(time.Nanosecond)

encoded := defaultAcceptanceTimestamp(acceptedAt)
decoded, err := time.Parse(time.RFC3339Nano, encoded)
if err != nil {
t.Fatal(err)
}
if !decoded.After(destroyedAt) {
t.Fatalf("accepted_at %q is not strictly after destroyed_at %q", encoded, destroyedAt.Format(time.RFC3339Nano))
}
if !decoded.Equal(acceptedAt) {
t.Fatalf("accepted_at = %q, want %q", encoded, acceptedAt.Format(time.RFC3339Nano))
}
}
6 changes: 4 additions & 2 deletions cmd/relay/proof_tool_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
//
// Run the fast compatibility checks with:
//
// (cd /path/to/proof-tool && bash scripts/bootstrap-vendor.sh)
// RELAY_PROOF_TOOL_DIR=/path/to/proof-tool go test ./cmd/relay \
// -run TestProofToolCompatibility -v
//
Expand Down Expand Up @@ -274,7 +275,8 @@ func testProofToolContributionCommands(t *testing.T, ceremonyBinary, fixtureRoot
"--coordinator-public-key-file", inspector.CoordinatorPublicKeyPath,
"--transcript-dir", ceremonyRoot, "--chain", chainPath,
"--chain-signature", chainSignature, "--candidate-dir", candidateDir,
"--coordinator-signing-key", coordinatorSigningKey, "--accepted-at", "2026-08-18T12:02:00Z")
"--coordinator-signing-key", coordinatorSigningKey,
"--accepted-at", defaultAcceptanceTimestamp(time.Now().Add(time.Second)))
accepted, err := inspector.Chain(
filepath.Join(ceremonyRoot, "phase1", "chain-0001.json"),
filepath.Join(ceremonyRoot, "phase1", "chain-0001.sig"),
Expand All @@ -300,7 +302,7 @@ func writeTestJSON(t *testing.T, path string, value any) {

func buildProofProgram(t *testing.T, proofToolDir, output, packagePath string) {
t.Helper()
command := exec.Command("go", "build", "-mod=mod", "-o", output, packagePath)
command := exec.Command("go", "build", "-mod=vendor", "-o", output, packagePath)
command.Dir = proofToolDir
if combined, err := command.CombinedOutput(); err != nil {
t.Fatalf("build proof-tool program %s: %v\n%s", packagePath, err, combined)
Expand Down
17 changes: 15 additions & 2 deletions docs/RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,12 +209,25 @@ exact clean checkout can build the CLI directly:
checkout --detach "$PROOF_TOOL_COMMIT"
(
cd "$RELEASE_EVIDENCE_ROOT/proof-tool-source"
CGO_ENABLED=0 go build -trimpath -buildvcs=true \
bash scripts/bootstrap-vendor.sh
CGO_ENABLED=0 go build -mod=vendor -trimpath -buildvcs=true \
-o "$RELEASE_EVIDENCE_ROOT/mpc-ceremony" ./cmd/mpc-ceremony
)

Never use `go run` for `mpc-ceremony`; the program requires VCS metadata that
`go run` omits.
`go run` omits. Do not replace the vendored build above with `-mod=mod`:
proof-tool's reviewed gnark changes are applied by `bootstrap-vendor.sh` and
must be present in the tested binary.

Before approving a coordinated release, exercise Relay against that exact
proof-tool checkout, including the production-sized contribution and
acceptance path:

cd /path/to/relay
RELAY_PROOF_TOOL_DIR="$RELEASE_EVIDENCE_ROOT/proof-tool-source" \
RELAY_PROOF_TOOL_FULL=1 \
go test ./cmd/relay -run '^TestProofToolCompatibility$' -count=1 \
-timeout 30m -v

For a production proof-tool release, follow its repository's approved signed
tag and reproducible-release procedure using
Expand Down