From 3b9fb137f937a848d53cd737f40267142a0f1921 Mon Sep 17 00:00:00 2001 From: Jason Park Date: Thu, 20 Aug 2026 16:24:45 +0000 Subject: [PATCH 1/3] release: validate ceremony kit binary pairs --- .../workflows/ceremony-kit-compatibility.yml | 112 ++++++++++++++ .github/workflows/ci.yml | 19 ++- docs/INSTALL.md | 11 +- docs/RELEASE.md | 22 ++- scripts/build-ceremony-kit.sh | 12 +- scripts/setup-ceremony-kit.sh | 7 +- .../mpc-ceremony-compatibility-stub.sh | 49 +++++++ scripts/verify-ceremony-kit-compatibility.sh | 138 ++++++++++++++++++ 8 files changed, 360 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/ceremony-kit-compatibility.yml create mode 100755 scripts/testdata/mpc-ceremony-compatibility-stub.sh create mode 100755 scripts/verify-ceremony-kit-compatibility.sh diff --git a/.github/workflows/ceremony-kit-compatibility.yml b/.github/workflows/ceremony-kit-compatibility.yml new file mode 100644 index 0000000..b156a27 --- /dev/null +++ b/.github/workflows/ceremony-kit-compatibility.yml @@ -0,0 +1,112 @@ +name: Ceremony kit binary compatibility + +on: + workflow_dispatch: + inputs: + relay_repository: + description: Approved Relay OWNER/REPOSITORY + required: true + default: zksecurity/relay + type: string + relay_tag: + description: Approved Relay release tag + required: true + type: string + relay_sha256: + description: Approved Relay binary SHA-256 + required: true + type: string + mpc_repository: + description: Approved proof-tool OWNER/REPOSITORY + required: true + default: Emurgo/proof-tool + type: string + mpc_tag: + description: Approved mpc-ceremony release tag + required: true + type: string + mpc_sha256: + description: Approved mpc-ceremony binary SHA-256 + required: true + type: string + +permissions: + contents: read + +concurrency: + group: ceremony-kit-compatibility-${{ github.run_id }} + cancel-in-progress: false + +jobs: + verify-released-binaries: + name: Verify exact released binary pair + runs-on: ubuntu-latest + timeout-minutes: 10 + env: + RELAY_REPOSITORY: ${{ inputs.relay_repository }} + RELAY_TAG: ${{ inputs.relay_tag }} + RELAY_SHA256: ${{ inputs.relay_sha256 }} + MPC_REPOSITORY: ${{ inputs.mpc_repository }} + MPC_TAG: ${{ inputs.mpc_tag }} + MPC_SHA256: ${{ inputs.mpc_sha256 }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Validate authenticated release inputs + shell: bash + run: | + set -euo pipefail + repository_pattern='^[A-Za-z0-9][A-Za-z0-9_.-]*/[A-Za-z0-9][A-Za-z0-9_.-]*$' + tag_pattern='^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$' + [[ "$RELAY_REPOSITORY" =~ $repository_pattern ]] + [[ "$MPC_REPOSITORY" =~ $repository_pattern ]] + [[ "$RELAY_TAG" =~ $tag_pattern ]] + [[ "$MPC_TAG" =~ $tag_pattern ]] + [[ "$RELAY_SHA256" =~ ^[0-9a-f]{64}$ ]] + [[ "$MPC_SHA256" =~ ^[0-9a-f]{64}$ ]] + + - name: Download and authenticate released binaries + shell: bash + run: | + set -euo pipefail + mkdir "$RUNNER_TEMP/ceremony-kit-inputs" + curl --proto '=https' --tlsv1.2 --fail --location --silent --show-error \ + "https://github.com/$RELAY_REPOSITORY/releases/download/$RELAY_TAG/relay" \ + --output "$RUNNER_TEMP/ceremony-kit-inputs/relay" + curl --proto '=https' --tlsv1.2 --fail --location --silent --show-error \ + "https://github.com/$MPC_REPOSITORY/releases/download/$MPC_TAG/mpc-ceremony" \ + --output "$RUNNER_TEMP/ceremony-kit-inputs/mpc-ceremony" + printf '%s %s\n' "$RELAY_SHA256" "$RUNNER_TEMP/ceremony-kit-inputs/relay" | \ + sha256sum --check --strict + printf '%s %s\n' "$MPC_SHA256" "$RUNNER_TEMP/ceremony-kit-inputs/mpc-ceremony" | \ + sha256sum --check --strict + chmod 0755 \ + "$RUNNER_TEMP/ceremony-kit-inputs/relay" \ + "$RUNNER_TEMP/ceremony-kit-inputs/mpc-ceremony" + + - name: Exercise the released CLI boundary + shell: bash + run: | + set -euo pipefail + mkdir "$RUNNER_TEMP/ceremony-kit-evidence" + scripts/verify-ceremony-kit-compatibility.sh \ + --relay-binary "$RUNNER_TEMP/ceremony-kit-inputs/relay" \ + --relay-sha256 "$RELAY_SHA256" \ + --mpc-binary "$RUNNER_TEMP/ceremony-kit-inputs/mpc-ceremony" \ + --mpc-sha256 "$MPC_SHA256" \ + --evidence-out "$RUNNER_TEMP/ceremony-kit-evidence/compatibility.json" + evidence_sha256=$(sha256sum \ + "$RUNNER_TEMP/ceremony-kit-evidence/compatibility.json") + { + printf '## Ceremony-kit compatibility passed\n\n' + printf -- '- Relay: `%s@%s` (`%s`)\n' \ + "$RELAY_REPOSITORY" "$RELAY_TAG" "$RELAY_SHA256" + printf -- '- mpc-ceremony: `%s@%s` (`%s`)\n' \ + "$MPC_REPOSITORY" "$MPC_TAG" "$MPC_SHA256" + printf -- '- Evidence SHA-256: `%s`\n\n' "${evidence_sha256%% *}" + printf '```json\n' + cat "$RUNNER_TEMP/ceremony-kit-evidence/compatibility.json" + printf '```\n' + } >>"$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8acd3b3..1c0f307 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,6 +82,7 @@ jobs: scripts/build-relay-release.sh \ scripts/build-ceremony-kit.sh \ scripts/setup-ceremony-kit.sh \ + scripts/verify-ceremony-kit-compatibility.sh \ scripts/storage-setup/*.sh \ scripts/verify-relay-release.sh \ scripts/verify-relay-reproducible.sh \ @@ -270,7 +271,18 @@ jobs: mkdir \ "$RUNNER_TEMP/ceremony-kit-parent" \ "$RUNNER_TEMP/ceremony-kit-install" \ - "$RUNNER_TEMP/ceremony-kit-operator" + "$RUNNER_TEMP/ceremony-kit-operator" \ + "$RUNNER_TEMP/incompatible-evidence" + if scripts/verify-ceremony-kit-compatibility.sh \ + --relay-binary "$RELEASE_DIR/relay" \ + --relay-sha256 "$RELAY_SHA256" \ + --mpc-binary "$RELEASE_DIR/relay" \ + --mpc-sha256 "$RELAY_SHA256" \ + --evidence-out "$RUNNER_TEMP/incompatible-evidence/compatibility.json" \ + >/dev/null 2>&1; then + echo "Relay was accepted as an mpc-ceremony binary" >&2 + exit 1 + fi if scripts/build-ceremony-kit.sh \ --mode production \ --relay-release-dir "$RELEASE_DIR" \ @@ -291,10 +303,10 @@ jobs: --relay-repository zksecurity/relay \ --relay-tag ci-rehearsal \ --relay-sha256 "$RELAY_SHA256" \ - --mpc-binary "$RELEASE_DIR/relay" \ + --mpc-binary scripts/testdata/mpc-ceremony-compatibility-stub.sh \ --mpc-repository zksecurity/proof-tool \ --mpc-tag ci-rehearsal \ - --mpc-sha256 "$RELAY_SHA256" \ + --mpc-sha256 "$(sha256sum scripts/testdata/mpc-ceremony-compatibility-stub.sh | cut -d ' ' -f 1)" \ --include-rehearsal \ --out-dir "$RUNNER_TEMP/ceremony-kit-parent/ceremony-kit" KIT_SOURCE_DATE_EPOCH=$(<"$RELEASE_DIR/source-date-epoch.txt") @@ -306,6 +318,7 @@ jobs: tar -tzf "$CEREMONY_KIT_ARCHIVE" >"$RUNNER_TEMP/ceremony-kit-contents.txt" grep -Fx 'ceremony-kit/' "$RUNNER_TEMP/ceremony-kit-contents.txt" grep -Fx 'ceremony-kit/setup' "$RUNNER_TEMP/ceremony-kit-contents.txt" + grep -Fx 'ceremony-kit/compatibility.json' "$RUNNER_TEMP/ceremony-kit-contents.txt" ! grep -Fx './setup' "$RUNNER_TEMP/ceremony-kit-contents.txt" mkdir "$RUNNER_TEMP/ceremony-kit-extracted" tar --no-same-owner -xzf "$CEREMONY_KIT_ARCHIVE" \ diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 3bb4d1b..6e061fd 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -23,7 +23,9 @@ protect against a compromised release account. The kit manifest records the independently approved Relay and proof-tool repositories, tags, and binary hashes. Operators do not enter those values -again. +again. The kit also contains `compatibility.json`, produced by running those +exact binaries together against the tiny signed rehearsal interface during kit +assembly. Neither source repository pins a commit from the other. ## Download and verify the kit @@ -65,8 +67,11 @@ cd "$CEREMONY_TOOLS_ROOT/ceremony-kit" ./setup verify ``` -`./setup verify` checks every internal file against the authenticated kit. Stop -here and choose exactly one of the two setup paths below. Do not run the +`./setup verify` checks every internal file against the authenticated kit and +confirms that `compatibility.json` names the hashes of the included binaries. +The release maintainer runs the compatibility exercise; operators do not need +either source checkout or to rerun it. Stop here and choose exactly one of the +two setup paths below. Do not run the production or non-rehearsal setup commands before the three-machine rehearsal setup. Every setup path installs the two pinned binaries in `/usr/local/bin`, using `sudo` only if necessary, and confirms both programs start. diff --git a/docs/RELEASE.md b/docs/RELEASE.md index 605a9e4..3a0636d 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -249,7 +249,9 @@ key. The kit is a convenience distribution assembled only after the Relay and proof-tool releases have been independently verified. It does not replace -either project's signed release package or reproducibility evidence. +either project's signed release package or reproducibility evidence. Neither +repository release is gated on a commit from the other repository. Compatibility +is a property of the exact released binary pair selected for this kit. Set the exact approved inputs: @@ -264,6 +266,21 @@ KIT_PARENT="$RELEASE_EVIDENCE_ROOT/ceremony-kit-parent" mkdir "$KIT_PARENT" ``` +The kit builder runs `scripts/verify-ceremony-kit-compatibility.sh` before it +packages anything. The verifier authenticates both supplied hashes, initializes +a fresh signed tiny rehearsal with the supplied `mpc-ceremony`, and has the +supplied Relay create participant profiles for both phases from the ceremony +CLI's authenticated projections. It performs no storage or network operation. +On success, the kit includes `compatibility.json`, binding the test name to the +two binary hashes; `setup verify` checks that binding. + +Maintainers may exercise a proposed pair before offline assembly with the +manual `Ceremony kit binary compatibility` GitHub Actions workflow. Supply the +two approved repositories, tags, and independently authenticated binary hashes. +The workflow contains no default commit pin and prints the resulting evidence +and its hash in the job summary. This hosted preview does not replace the +independent release verification or the gate rerun by the kit builder. + For production, assemble only the two binaries and their release manifest: ```bash @@ -297,7 +314,8 @@ sha256sum "$CEREMONY_KIT_ARCHIVE" ``` Extract the archive into a fresh directory and run `ceremony-kit/setup verify` -before publishing it. Publish the archive with the exact asset name +before publishing it. Retain `compatibility.json` and the verifier output with +the coordinated release evidence. Publish the archive with the exact asset name `ceremony-kit-linux-amd64.tar.gz`. Announce only its tag and SHA-256 to normal operators through the independent authenticated channel; `release.json` inside the kit records all underlying repositories, tags, and hashes. diff --git a/scripts/build-ceremony-kit.sh b/scripts/build-ceremony-kit.sh index 13baf06..0d11a59 100755 --- a/scripts/build-ceremony-kit.sh +++ b/scripts/build-ceremony-kit.sh @@ -2,6 +2,8 @@ set -euo pipefail umask 077 +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) + die() { printf 'FAIL: %s\n' "$*" >&2 exit 1 @@ -44,6 +46,7 @@ done -n "$MPC_BINARY" && -n "$MPC_REPOSITORY" && -n "$MPC_TAG" && -n "$MPC_SHA256" && -n "$OUT_DIR" ]] || usage [[ "$MODE" == rehearsal || "$INCLUDE_REHEARSAL" == no ]] || die "production kits cannot include rehearsal scripts" [[ "$INCLUDE_REHEARSAL" == no || "$MODE" == rehearsal ]] || die "--include-rehearsal requires rehearsal mode" +[[ "$MODE" == production || "$INCLUDE_REHEARSAL" == yes ]] || die "rehearsal kits require --include-rehearsal" repository_pattern='^[A-Za-z0-9][A-Za-z0-9_.-]*/[A-Za-z0-9][A-Za-z0-9_.-]*$' tag_pattern='^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$' @@ -95,6 +98,13 @@ install -m 0755 "$RELAY_RELEASE_DIR/relay" "$staging/relay" install -m 0755 "$MPC_BINARY" "$staging/mpc-ceremony" install -m 0644 "$RELAY_RELEASE_DIR/storage-setup.tar.gz" "$staging/storage-setup.tar.gz" +"$SCRIPT_DIR/verify-ceremony-kit-compatibility.sh" \ + --relay-binary "$staging/relay" \ + --relay-sha256 "$RELAY_SHA256" \ + --mpc-binary "$staging/mpc-ceremony" \ + --mpc-sha256 "$MPC_SHA256" \ + --evidence-out "$staging/compatibility.json" + REHEARSAL_SHA=none if [[ "$INCLUDE_REHEARSAL" == yes ]]; then install -m 0644 "$RELAY_RELEASE_DIR/three-machine-rehearsal.tar.gz" "$staging/three-machine-rehearsal.tar.gz" @@ -119,7 +129,7 @@ printf '{\n "schema": "ceremony-kit-v1",\n "mode": "%s",\n "relay": {\n "r ( cd "$staging" - checksum_files=(mpc-ceremony relay release.env release.json setup storage-setup.tar.gz) + checksum_files=(compatibility.json mpc-ceremony relay release.env release.json setup storage-setup.tar.gz) [[ "$INCLUDE_REHEARSAL" == yes ]] && checksum_files+=(three-machine-rehearsal.tar.gz) LC_ALL=C sha256sum "${checksum_files[@]}" >checksums.sha256 ) diff --git a/scripts/setup-ceremony-kit.sh b/scripts/setup-ceremony-kit.sh index 352d2a9..d844969 100755 --- a/scripts/setup-ceremony-kit.sh +++ b/scripts/setup-ceremony-kit.sh @@ -65,7 +65,7 @@ for command_name in install sha256sum; do command -v "$command_name" >/dev/null 2>&1 || die "required command is missing: $command_name" done -for name in checksums.sha256 release.env release.json relay mpc-ceremony setup storage-setup.tar.gz; do +for name in checksums.sha256 compatibility.json release.env release.json relay mpc-ceremony setup storage-setup.tar.gz; do path="$KIT_ROOT/$name" [[ -f "$path" && ! -L "$path" ]] || die "kit entry is missing or unsafe: $name" done @@ -121,6 +121,11 @@ expected_json=$(printf '{\n "schema": "ceremony-kit-v1",\n "mode": "%s",\n "r "$MPC_RELEASE_REPOSITORY" "$MPC_TAG" "$MPC_SHA256" "$REHEARSAL_ARCHIVE_SHA256") [[ "$(<"$KIT_ROOT/release.json")" == "$expected_json" ]] || die "release.json does not match release.env" +expected_compatibility=$(printf '{\n "schema": "ceremony-kit-compatibility-v1",\n "test": "tiny-rehearsal-participant-config-v1",\n "relay_sha256": "%s",\n "mpc_ceremony_sha256": "%s"\n}' \ + "$RELAY_SHA256" "$MPC_SHA256") +[[ "$(<"$KIT_ROOT/compatibility.json")" == "$expected_compatibility" ]] || + die "compatibility.json does not match the kit binaries" + printf 'Verified %s ceremony kit:\n' "$KIT_MODE" printf ' Relay: %s@%s (%s)\n' "$RELAY_REPOSITORY" "$RELAY_TAG" "$RELAY_SHA256" printf ' mpc-ceremony: %s@%s (%s)\n' "$MPC_RELEASE_REPOSITORY" "$MPC_TAG" "$MPC_SHA256" diff --git a/scripts/testdata/mpc-ceremony-compatibility-stub.sh b/scripts/testdata/mpc-ceremony-compatibility-stub.sh new file mode 100755 index 0000000..331b727 --- /dev/null +++ b/scripts/testdata/mpc-ceremony-compatibility-stub.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# Minimal CLI-contract fixture for Relay's own ceremony-kit tests. Real kit +# validation always supplies an independently released mpc-ceremony binary. +set -euo pipefail + +ceremony_id=sha256:1111111111111111111111111111111111111111111111111111111111111111 + +if [[ ${1:-} == rehearsal && ${2:-} == init ]]; then + out_dir= + shift 2 + while [[ $# -gt 0 ]]; do + case "$1" in + --created-at) shift 2 ;; + --out-dir) out_dir=$2; shift 2 ;; + *) exit 2 ;; + esac + done + [[ -n "$out_dir" && ! -e "$out_dir" ]] || exit 1 + mkdir -p "$out_dir/public" "$out_dir/config" "$out_dir/keys" + printf '{}\n' >"$out_dir/public/ceremony.json" + printf 'fixture\n' >"$out_dir/public/ceremony.sig" + printf 'fixture\n' >"$out_dir/public/coordinator-public-key.hex" + printf '{}\n' >"$out_dir/config/environment.json" + printf 'fixture\n' >"$out_dir/keys/participant-01.ed25519.private.hex" + chmod 0600 "$out_dir/keys/participant-01.ed25519.private.hex" + exit 0 +fi + +if [[ ${1:-} == --format && ${2:-} == json && ${3:-} == inspect ]]; then + case "${4:-}" in + definition) + printf '{"schema":"proof-tool-mpc-command-result-v1","ok":true,"command":"inspect definition","ceremony_id":"%s","definition_inspection":{"schema":"proof-tool-mpc-definition-inspection-v1","ceremony_id":"%s","mode":"rehearsal","phase1_participants":["participant-01"],"phase2_participants":["participant-01"],"r1cs":{"name":"fixture.ccs","digest":{"sha256":"sha256:2222222222222222222222222222222222222222222222222222222222222222","size":1}}}}\n' \ + "$ceremony_id" "$ceremony_id" + ;; + participant) + printf '{"schema":"proof-tool-mpc-command-result-v1","ok":true,"command":"inspect participant","ceremony_id":"%s","participant_inspection":{"schema":"proof-tool-mpc-participant-inspection-v1","ceremony_id":"%s","participant_id":"participant-01","key_id":"participant-01-key","public_key_fingerprint":"sha256:3333333333333333333333333333333333333333333333333333333333333333","phase1_position":1,"phase2_position":1}}\n' \ + "$ceremony_id" "$ceremony_id" + ;; + *) exit 2 ;; + esac + exit 0 +fi + +if [[ ${1:-} == help || ${1:-} == --help || ${1:-} == -h ]]; then + printf 'mpc-ceremony compatibility fixture\n' + exit 0 +fi + +exit 2 diff --git a/scripts/verify-ceremony-kit-compatibility.sh b/scripts/verify-ceremony-kit-compatibility.sh new file mode 100755 index 0000000..34e15b2 --- /dev/null +++ b/scripts/verify-ceremony-kit-compatibility.sh @@ -0,0 +1,138 @@ +#!/usr/bin/env bash +# Exercise the released Relay and mpc-ceremony binaries together without a +# source checkout, network storage, or a repository-to-repository pin. +set -euo pipefail +umask 077 + +die() { + printf 'FAIL: %s\n' "$*" >&2 + exit 1 +} + +usage() { + die "usage: $0 --relay-binary FILE --relay-sha256 HEX --mpc-binary FILE --mpc-sha256 HEX --evidence-out FILE" +} + +RELAY_BINARY= +RELAY_SHA256= +MPC_BINARY= +MPC_SHA256= +EVIDENCE_OUT= +while [[ $# -gt 0 ]]; do + case "$1" in + --relay-binary) [[ $# -ge 2 ]] || usage; RELAY_BINARY=$2; shift 2 ;; + --relay-sha256) [[ $# -ge 2 ]] || usage; RELAY_SHA256=$2; shift 2 ;; + --mpc-binary) [[ $# -ge 2 ]] || usage; MPC_BINARY=$2; shift 2 ;; + --mpc-sha256) [[ $# -ge 2 ]] || usage; MPC_SHA256=$2; shift 2 ;; + --evidence-out) [[ $# -ge 2 ]] || usage; EVIDENCE_OUT=$2; shift 2 ;; + *) usage ;; + esac +done + +[[ -n "$RELAY_BINARY" && -n "$RELAY_SHA256" && -n "$MPC_BINARY" && + -n "$MPC_SHA256" && -n "$EVIDENCE_OUT" ]] || usage +[[ "$RELAY_SHA256" =~ ^[0-9a-f]{64}$ ]] || die "Relay SHA-256 must be 64 lowercase hexadecimal characters" +[[ "$MPC_SHA256" =~ ^[0-9a-f]{64}$ ]] || die "mpc-ceremony SHA-256 must be 64 lowercase hexadecimal characters" + +for command_name in basename chmod dirname grep install mkdir mktemp realpath rm sed sha256sum; do + command -v "$command_name" >/dev/null 2>&1 || die "required command is missing: $command_name" +done +for binary in "$RELAY_BINARY" "$MPC_BINARY"; do + [[ -f "$binary" && ! -L "$binary" && -x "$binary" ]] || + die "compatibility input must be an executable non-symlink regular file: $binary" +done +printf '%s %s\n' "$RELAY_SHA256" "$RELAY_BINARY" | sha256sum --check --strict +printf '%s %s\n' "$MPC_SHA256" "$MPC_BINARY" | sha256sum --check --strict + +evidence_parent_input=$(dirname -- "$EVIDENCE_OUT") +[[ -d "$evidence_parent_input" && ! -L "$evidence_parent_input" ]] || + die "evidence parent must be an existing non-symlink directory" +evidence_parent=$(realpath -e -- "$evidence_parent_input") +EVIDENCE_OUT="$evidence_parent/$(basename -- "$EVIDENCE_OUT")" +[[ ! -e "$EVIDENCE_OUT" && ! -L "$EVIDENCE_OUT" ]] || + die "evidence output already exists: $EVIDENCE_OUT" + +temp_parent_input=${TMPDIR:-/tmp} +[[ -d "$temp_parent_input" && ! -L "$temp_parent_input" ]] || + die "TMPDIR must be an existing non-symlink directory" +temp_parent=$(realpath -e -- "$temp_parent_input") +work_root=$(mktemp -d "$temp_parent/ceremony-kit-compatibility.XXXXXXXX") +cleanup() { + if [[ -n "${work_root:-}" && "$work_root" == "$temp_parent"/ceremony-kit-compatibility.* ]]; then + rm -rf -- "$work_root" + fi +} +trap cleanup EXIT + +mkdir "$work_root/bin" +install -m 0755 "$RELAY_BINARY" "$work_root/bin/relay" +install -m 0755 "$MPC_BINARY" "$work_root/bin/mpc-ceremony" +relay="$work_root/bin/relay" +mpc="$work_root/bin/mpc-ceremony" +rehearsal="$work_root/rehearsal" + +"$mpc" rehearsal init \ + --created-at 2026-08-20T06:00:00Z \ + --out-dir "$rehearsal" >/dev/null + +for path in \ + "$rehearsal/public/ceremony.json" \ + "$rehearsal/public/ceremony.sig" \ + "$rehearsal/public/coordinator-public-key.hex" \ + "$rehearsal/config/environment.json" \ + "$rehearsal/keys/participant-01.ed25519.private.hex"; do + [[ -f "$path" && ! -L "$path" ]] || die "rehearsal initializer output is absent or unsafe: $path" +done + +definition_json=$("$mpc" --format json inspect definition \ + --ceremony "$rehearsal/public/ceremony.json" \ + --ceremony-signature "$rehearsal/public/ceremony.sig" \ + --coordinator-public-key-file "$rehearsal/public/coordinator-public-key.hex") +[[ "$definition_json" == *'"schema":"proof-tool-mpc-command-result-v1"'* && + "$definition_json" == *'"ok":true'* && + "$definition_json" == *'"schema":"proof-tool-mpc-definition-inspection-v1"'* && + "$definition_json" == *'"phase1_participants":["participant-01"'* ]] || + die "mpc-ceremony did not emit the expected authenticated definition projection" +ceremony_id=$(printf '%s\n' "$definition_json" | + sed -n 's/.*"ceremony_id":"\(sha256:[0-9a-f]\{64\}\)".*/\1/p') +[[ "$ceremony_id" =~ ^sha256:[0-9a-f]{64}$ ]] || + die "mpc-ceremony definition projection did not contain one valid ceremony ID" + +storage="$rehearsal/config/relay-storage.json" +printf '{\n "schema": "relay-storage-config-v1",\n "provider": "r2",\n "ceremony_id": "%s",\n "endpoint": "https://compatibility.r2.cloudflarestorage.com",\n "region": "auto",\n "account_id": "compatibility",\n "parent_access_key_id": "compatibility",\n "published_bucket": "compatibility-published",\n "published_base_url": "https://compatibility.invalid",\n "inbox_bucket": "compatibility-inbox",\n "coordinator_profile": "compatibility",\n "ceremony": "%s",\n "ceremony_signature": "%s",\n "coordinator_public_key": "%s",\n "ceremony_binary": "%s"\n}\n' \ + "$ceremony_id" \ + "$rehearsal/public/ceremony.json" \ + "$rehearsal/public/ceremony.sig" \ + "$rehearsal/public/coordinator-public-key.hex" \ + "$mpc" >"$storage" +chmod 0600 "$storage" + +for phase in phase1 phase2; do + profile="$rehearsal/config/participant-$phase.relay.json" + HOME="$work_root/home" "$relay" ceremony init-config \ + --home "$rehearsal" \ + --role participant \ + --identity participant-01 \ + --phase "$phase" \ + --storage "$storage" \ + --coordinator-key "$rehearsal/public/coordinator-public-key.hex" \ + --ceremony-binary "$mpc" \ + --signing-key "$rehearsal/keys/participant-01.ed25519.private.hex" \ + --environment "$rehearsal/config/environment.json" \ + --out "$profile" >/dev/null + [[ -f "$profile" && ! -L "$profile" ]] || die "Relay did not create the $phase participant profile" + grep -Eq '"schema"[[:space:]]*:[[:space:]]*"relay-role-config-v1"' "$profile" || + die "Relay emitted an unexpected role profile schema" + grep -Eq '"identity_id"[[:space:]]*:[[:space:]]*"participant-01"' "$profile" || + die "Relay emitted an unexpected participant identity" + grep -Eq "\"phase\"[[:space:]]*:[[:space:]]*\"$phase\"" "$profile" || + die "Relay emitted an unexpected participant phase" +done + +( + set -o noclobber + printf '{\n "schema": "ceremony-kit-compatibility-v1",\n "test": "tiny-rehearsal-participant-config-v1",\n "relay_sha256": "%s",\n "mpc_ceremony_sha256": "%s"\n}\n' \ + "$RELAY_SHA256" "$MPC_SHA256" >"$EVIDENCE_OUT" +) +chmod 0444 "$EVIDENCE_OUT" +printf 'Verified ceremony-kit binary compatibility.\nEvidence: %s\n' "$EVIDENCE_OUT" From 1b9a663ac6d1bd73c38ab81c6d37b59057b1f8db Mon Sep 17 00:00:00 2001 From: Jason Park Date: Thu, 20 Aug 2026 16:36:41 +0000 Subject: [PATCH 2/3] docs: align release guide with binary compatibility --- docs/RELEASE.md | 55 ++++++++++++++----------------------------------- 1 file changed, 16 insertions(+), 39 deletions(-) diff --git a/docs/RELEASE.md b/docs/RELEASE.md index 3a0636d..52f4e14 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -205,45 +205,22 @@ Then rebuild without access to the build-signing private key: Retain the verification output and reproduced package with the release audit evidence. -## Build and audit `mpc-ceremony` - -The proof-tool repository owns its release process. For a local rehearsal, an -exact clean checkout can build the CLI directly: - - : "${PROOF_TOOL_COMMIT:?Set PROOF_TOOL_COMMIT to the approved full commit ID}" - printf '%s\n' "$PROOF_TOOL_COMMIT" | grep -Eq '^[0-9a-f]{40}$' - git clone https://github.com/Emurgo/proof-tool.git \ - "$RELEASE_EVIDENCE_ROOT/proof-tool-source" - git -C "$RELEASE_EVIDENCE_ROOT/proof-tool-source" \ - checkout --detach "$PROOF_TOOL_COMMIT" - ( - cd "$RELEASE_EVIDENCE_ROOT/proof-tool-source" - 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. 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 55m -v - -For a production proof-tool release, follow its repository's approved signed -tag and reproducible-release procedure using -`scripts/build-mpc-ceremony-release.sh` and -`scripts/verify-mpc-ceremony-reproducible.sh`. The proof-tool release maintainer, -not the ceremony coordinator or a participant, owns its build-signing private -key. +## Verify the independent `mpc-ceremony` release + +The proof-tool repository owns the `mpc-ceremony` release process. Obtain the +standalone binary and complete verification package from the approved +proof-tool release. Authenticate its repository, signed tag, and published +hashes through the release trust channel, then follow proof-tool's +`docs/mpc-ceremony-release.md` and reproducible-release procedure. The +proof-tool release maintainer, not the ceremony coordinator or a participant, +owns its build-signing private key. + +Retain the verification output with the release audit evidence and set +`MPC_BINARY` below to that verified binary. Record its approved repository, +tag, and SHA-256 independently; do not select or pin a proof-tool source commit +in Relay's release process. Relay's source-coupled proof-tool integration test +is a developer diagnostic, not a coordinated-release gate. Compatibility is +tested from the exact released binaries during ceremony-kit assembly. ## Assemble the coordinated ceremony kit From 6f8a9163c485f74fec881d47373c93096ef9a3c9 Mon Sep 17 00:00:00 2001 From: Jason Park Date: Thu, 20 Aug 2026 16:55:05 +0000 Subject: [PATCH 3/3] feat: validate ceremony pair contribution flow --- cmd/relay/compatibility.go | 121 ++++++++++++++++++ cmd/relay/coordinator_workflow.go | 48 ++++--- cmd/relay/coordinator_workflow_test.go | 31 +++++ cmd/relay/main.go | 2 + cmd/relay/role.go | 6 +- cmd/relay/workflow_commands.go | 6 +- docs/INSTALL.md | 6 +- docs/RELEASE.md | 7 +- scripts/setup-ceremony-kit.sh | 2 +- .../mpc-ceremony-compatibility-stub.sh | 59 ++++++++- scripts/verify-ceremony-kit-compatibility.sh | 9 +- 11 files changed, 272 insertions(+), 25 deletions(-) create mode 100644 cmd/relay/compatibility.go diff --git a/cmd/relay/compatibility.go b/cmd/relay/compatibility.go new file mode 100644 index 0000000..613389c --- /dev/null +++ b/cmd/relay/compatibility.go @@ -0,0 +1,121 @@ +package main + +import ( + "errors" + "flag" + "fmt" + "os" + "path/filepath" + "time" + + "github.com/zksecurity/relay/internal/transcript" +) + +// runVerifyCeremonyPair is intentionally hidden under advanced. Release-kit +// assembly uses it to exercise the exact Relay and mpc-ceremony binaries +// against proof-tool's same-host tiny rehearsal. It never accesses storage and +// refuses non-rehearsal definitions. +func runVerifyCeremonyPair(args []string) error { + set := flag.NewFlagSet("advanced verify-ceremony-pair", flag.ContinueOnError) + var home, ceremonyBinary string + set.StringVar(&home, "home", "", "fresh rehearsal root created by mpc-ceremony rehearsal init") + set.StringVar(&ceremonyBinary, "ceremony-binary", "", "exact mpc-ceremony release binary") + if err := set.Parse(args); err != nil { + return err + } + if home == "" || ceremonyBinary == "" { + return errors.New("--home and --ceremony-binary are required") + } + if !filepath.IsAbs(home) || filepath.Clean(home) != home { + return errors.New("--home must be an absolute clean path") + } + if info, err := os.Lstat(ceremonyBinary); err != nil || !info.Mode().IsRegular() || info.Mode()&0o111 == 0 { + return errors.New("--ceremony-binary must be an executable non-symlink regular file") + } + + root := filepath.Join(home, "public") + o := roleOpts{ + root: root, + definition: filepath.Join(root, "ceremony.json"), + definitionSig: filepath.Join(root, "ceremony.sig"), + coordinatorKey: filepath.Join(root, "coordinator-public-key.hex"), + ceremonyBinary: ceremonyBinary, + phase: "phase1", + role: "participant-01", + signingKey: filepath.Join(home, "keys", "participant-01.ed25519.private.hex"), + envPath: filepath.Join(home, "config", "environment.json"), + outDir: filepath.Join(home, "compatibility", "phase1-participant-01"), + } + inspector := transcript.Inspector{ + Executable: ceremonyBinary, CeremonyPath: o.definition, + CeremonySignaturePath: o.definitionSig, CoordinatorPublicKeyPath: o.coordinatorKey, + TranscriptRoot: root, + } + definition, err := inspector.Definition() + if err != nil { + return fmt.Errorf("inspect rehearsal definition: %w", err) + } + if definition.Mode != "rehearsal" { + return fmt.Errorf("compatibility exercise requires rehearsal mode, found %q", definition.Mode) + } + participant, err := inspector.Participant(o.signingKey) + if err != nil { + return fmt.Errorf("inspect rehearsal participant: %w", err) + } + if participant.ParticipantID != o.role { + return fmt.Errorf("rehearsal key belongs to %s, want %s", participant.ParticipantID, o.role) + } + + chainPath := filepath.Join(root, "phase1", "chain-0000.json") + chainSignaturePath := filepath.Join(root, "phase1", "chain-0000.sig") + chain, err := inspector.Chain(chainPath, chainSignaturePath) + if err != nil { + return fmt.Errorf("inspect initial rehearsal chain: %w", err) + } + if chain.Phase != o.phase || chain.AcceptedCount() != 0 { + return fmt.Errorf("initial rehearsal chain is %s with %d accepted contributions", chain.Phase, chain.AcceptedCount()) + } + nextID, nextIndex, err := definition.NextContributor(o.phase, chain.AcceptedCount()) + if err != nil { + return err + } + if nextID != o.role || nextIndex != 1 { + return fmt.Errorf("initial rehearsal turn is %s at index %d, want %s at index 1", nextID, nextIndex, o.role) + } + if err := os.Mkdir(filepath.Dir(o.outDir), 0o700); err != nil { + return fmt.Errorf("create compatibility output parent: %w", err) + } + pos := position{ + definition: definition, chain: chain, chainPath: chainPath, + nextID: nextID, nextIndex: nextIndex, + } + contributedAt := time.Now().UTC().Truncate(time.Second) + if err := runNextAt(o, pos, contributedAt); err != nil { + return fmt.Errorf("run tiny rehearsal contribution: %w", err) + } + if err := runErasureAt(o, contributedAt.Add(time.Second)); err != nil { + return fmt.Errorf("create tiny rehearsal erasure attestation: %w", err) + } + + cmd := candidateVerificationCommand( + o, chainPath, chainSignaturePath, o.outDir, + filepath.Join(home, "keys", "coordinator.ed25519.private.hex"), + defaultAcceptanceTimestamp(contributedAt.Add(2*time.Second)), + ) + cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr + if err := runWithProgress("verifying tiny rehearsal contribution", cmd.Run); err != nil { + return err + } + accepted, err := inspector.Chain( + filepath.Join(root, "phase1", "chain-0001.json"), + filepath.Join(root, "phase1", "chain-0001.sig"), + ) + if err != nil { + return fmt.Errorf("inspect accepted rehearsal chain: %w", err) + } + if accepted.AcceptedCount() != 1 || accepted.Records[0].ParticipantID != o.role { + return fmt.Errorf("accepted rehearsal chain does not contain %s at index 1", o.role) + } + fmt.Println("verified exact-binary phase1 contribution and acceptance") + return nil +} diff --git a/cmd/relay/coordinator_workflow.go b/cmd/relay/coordinator_workflow.go index 57b68a1..bb3555e 100644 --- a/cmd/relay/coordinator_workflow.go +++ b/cmd/relay/coordinator_workflow.go @@ -116,7 +116,8 @@ func runAcceptCandidate(args []string) error { root: root, definition: config.CeremonyPath, definitionSig: config.CeremonySignature, coordinatorKey: config.CoordinatorPublicKey, ceremonyBinary: config.CeremonyBinary, phase: manifest.Phase, role: manifest.ParticipantID, - client: store.Client{Bucket: config.PublishedBucket, PublicBaseURL: config.PublishedBaseURL}, + client: store.Client{Bucket: config.PublishedBucket, PublicBaseURL: config.PublishedBaseURL}, + phase1Seal: phase1Seal, phase1SealSig: phase1SealSignature, } pos, err := resolvePosition(o) if err != nil { @@ -170,20 +171,10 @@ func runAcceptCandidate(args []string) error { 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, - "--candidate-dir", candidateDir, "--coordinator-signing-key", coordinatorSigningKey, "--accepted-at", acceptedAt} - if manifest.Phase == "phase2" { - if phase1Seal == "" { - phase1Seal = filepath.Join(root, "phase1", "sealed", "seal.json") - } - if phase1SealSignature == "" { - phase1SealSignature = filepath.Join(root, "phase1", "sealed", "seal.sig") - } - command = append(command, "--phase1-seal", phase1Seal, "--phase1-seal-signature", phase1SealSignature) - } - cmd := exec.Command(config.CeremonyBinary, command...) + cmd := candidateVerificationCommand( + o, pos.chainPath, pos.chain.ChainSignaturePath, candidateDir, + coordinatorSigningKey, acceptedAt, + ) cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr if err := runWithProgress("verifying contribution candidate", cmd.Run); err != nil { return err @@ -211,6 +202,33 @@ func defaultAcceptanceTimestamp(now time.Time) string { return now.UTC().Format(time.RFC3339Nano) } +// candidateVerificationCommand is shared by the coordinator acceptance path +// and the source-free release-pair gate. Keeping the argv construction here +// prevents the release gate from testing a second approximation of Relay's +// mpc-ceremony interface. +func candidateVerificationCommand( + o roleOpts, + chainPath, chainSignaturePath, candidateDir, coordinatorSigningKey, acceptedAt string, +) *exec.Cmd { + command := []string{o.phase, "verify", "--ceremony", o.definition, + "--ceremony-signature", o.definitionSig, "--coordinator-public-key-file", o.coordinatorKey, + "--transcript-dir", o.root, "--chain", chainPath, "--chain-signature", chainSignaturePath, + "--candidate-dir", candidateDir, "--coordinator-signing-key", coordinatorSigningKey, + "--accepted-at", acceptedAt} + if o.phase == "phase2" { + phase1Seal := o.phase1Seal + phase1SealSignature := o.phase1SealSig + if phase1Seal == "" { + phase1Seal = filepath.Join(o.root, "phase1", "sealed", "seal.json") + } + if phase1SealSignature == "" { + phase1SealSignature = filepath.Join(o.root, "phase1", "sealed", "seal.sig") + } + command = append(command, "--phase1-seal", phase1Seal, "--phase1-seal-signature", phase1SealSignature) + } + return exec.Command(o.ceremonyExecutable(), command...) +} + func runEvidenceInbox(args []string) error { set := flag.NewFlagSet("coordinator evidence", flag.ContinueOnError) var storagePath, role string diff --git a/cmd/relay/coordinator_workflow_test.go b/cmd/relay/coordinator_workflow_test.go index d39b37a..fd6c533 100644 --- a/cmd/relay/coordinator_workflow_test.go +++ b/cmd/relay/coordinator_workflow_test.go @@ -1,6 +1,7 @@ package main import ( + "reflect" "testing" "time" ) @@ -21,3 +22,33 @@ func TestDefaultAcceptanceTimestampPreservesSubsecondOrdering(t *testing.T) { t.Fatalf("accepted_at = %q, want %q", encoded, acceptedAt.Format(time.RFC3339Nano)) } } + +func TestCandidateVerificationCommandUsesOperationalCLI(t *testing.T) { + o := roleOpts{ + root: "/ceremony", definition: "/ceremony/ceremony.json", + definitionSig: "/ceremony/ceremony.sig", coordinatorKey: "/trust/coordinator.hex", + ceremonyBinary: "/trusted/mpc-ceremony", phase: "phase2", + phase1Seal: "/sealed/phase1.json", phase1SealSig: "/sealed/phase1.sig", + } + command := candidateVerificationCommand( + o, "/ceremony/phase2/chain-0000.json", "/ceremony/phase2/chain-0000.sig", + "/candidate", "/keys/coordinator.private.hex", "2026-08-20T12:00:02Z", + ) + want := []string{ + "/trusted/mpc-ceremony", "phase2", "verify", + "--ceremony", "/ceremony/ceremony.json", + "--ceremony-signature", "/ceremony/ceremony.sig", + "--coordinator-public-key-file", "/trust/coordinator.hex", + "--transcript-dir", "/ceremony", + "--chain", "/ceremony/phase2/chain-0000.json", + "--chain-signature", "/ceremony/phase2/chain-0000.sig", + "--candidate-dir", "/candidate", + "--coordinator-signing-key", "/keys/coordinator.private.hex", + "--accepted-at", "2026-08-20T12:00:02Z", + "--phase1-seal", "/sealed/phase1.json", + "--phase1-seal-signature", "/sealed/phase1.sig", + } + if !reflect.DeepEqual(command.Args, want) { + t.Fatalf("candidate verification argv = %#v, want %#v", command.Args, want) + } +} diff --git a/cmd/relay/main.go b/cmd/relay/main.go index 31090be..9000e09 100644 --- a/cmd/relay/main.go +++ b/cmd/relay/main.go @@ -234,6 +234,8 @@ func runAdvanced(args []string) error { return runPush(args[1:]) case "pull": return runPull(args[1:]) + case "verify-ceremony-pair": + return runVerifyCeremonyPair(args[1:]) default: return fmt.Errorf("unknown advanced command %q", args[0]) } diff --git a/cmd/relay/role.go b/cmd/relay/role.go index fb0e1e3..86b8831 100644 --- a/cmd/relay/role.go +++ b/cmd/relay/role.go @@ -339,6 +339,10 @@ func mustSchedule(pos position, phase string) []string { // confirming the coordinator public key and binary hash arrived over a trusted // channel, and that happens once at setup rather than per contribution. func runNext(o roleOpts, pos position) error { + return runNextAt(o, pos, time.Now()) +} + +func runNextAt(o roleOpts, pos position, contributedAt time.Time) error { command := []string{ o.ceremonyExecutable(), o.phase, "contribute", "--ceremony", o.definition, @@ -350,7 +354,7 @@ func runNext(o roleOpts, pos position) error { "--participant-id", o.role, "--participant-signing-key", o.signingKey, "--environment", o.envPath, - "--contributed-at", time.Now().UTC().Format(time.RFC3339), + "--contributed-at", contributedAt.UTC().Format(time.RFC3339), "--out-dir", o.outDir, } diff --git a/cmd/relay/workflow_commands.go b/cmd/relay/workflow_commands.go index 65b242f..4d1cbd3 100644 --- a/cmd/relay/workflow_commands.go +++ b/cmd/relay/workflow_commands.go @@ -301,10 +301,14 @@ func confirmErasure() error { } func runErasure(o roleOpts) error { + return runErasureAt(o, time.Now()) +} + +func runErasureAt(o roleOpts, destroyedAt time.Time) error { argv := []string{o.phase, "attest-erasure", "--ceremony", o.definition, "--ceremony-signature", o.definitionSig, "--coordinator-public-key-file", o.coordinatorKey, "--participant-id", o.role, "--participant-signing-key", o.signingKey, - "--candidate-dir", o.outDir, "--destroyed-at", time.Now().UTC().Format(time.RFC3339)} + "--candidate-dir", o.outDir, "--destroyed-at", destroyedAt.UTC().Format(time.RFC3339)} cmd := exec.Command(o.ceremonyExecutable(), argv...) cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr return runWithProgress("creating erasure attestation", cmd.Run) diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 6e061fd..089e9dd 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -24,8 +24,10 @@ protect against a compromised release account. The kit manifest records the independently approved Relay and proof-tool repositories, tags, and binary hashes. Operators do not enter those values again. The kit also contains `compatibility.json`, produced by running those -exact binaries together against the tiny signed rehearsal interface during kit -assembly. Neither source repository pins a commit from the other. +exact binaries together through participant configuration, a real tiny phase 1 +contribution, erasure attestation, coordinator acceptance, and accepted-chain +inspection during kit assembly. Neither source repository pins a commit from +the other. ## Download and verify the kit diff --git a/docs/RELEASE.md b/docs/RELEASE.md index 52f4e14..e59a26f 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -246,8 +246,11 @@ mkdir "$KIT_PARENT" The kit builder runs `scripts/verify-ceremony-kit-compatibility.sh` before it packages anything. The verifier authenticates both supplied hashes, initializes a fresh signed tiny rehearsal with the supplied `mpc-ceremony`, and has the -supplied Relay create participant profiles for both phases from the ceremony -CLI's authenticated projections. It performs no storage or network operation. +supplied Relay create participant profiles for both phases from authenticated +definition and participant projections. Relay then uses its operational +contribution, erasure, and candidate-verification command builders to produce +and accept the first tiny phase 1 contribution, after which it authenticates +the resulting chain projection. It performs no storage or network operation. On success, the kit includes `compatibility.json`, binding the test name to the two binary hashes; `setup verify` checks that binding. diff --git a/scripts/setup-ceremony-kit.sh b/scripts/setup-ceremony-kit.sh index d844969..a3b5d12 100755 --- a/scripts/setup-ceremony-kit.sh +++ b/scripts/setup-ceremony-kit.sh @@ -121,7 +121,7 @@ expected_json=$(printf '{\n "schema": "ceremony-kit-v1",\n "mode": "%s",\n "r "$MPC_RELEASE_REPOSITORY" "$MPC_TAG" "$MPC_SHA256" "$REHEARSAL_ARCHIVE_SHA256") [[ "$(<"$KIT_ROOT/release.json")" == "$expected_json" ]] || die "release.json does not match release.env" -expected_compatibility=$(printf '{\n "schema": "ceremony-kit-compatibility-v1",\n "test": "tiny-rehearsal-participant-config-v1",\n "relay_sha256": "%s",\n "mpc_ceremony_sha256": "%s"\n}' \ +expected_compatibility=$(printf '{\n "schema": "ceremony-kit-compatibility-v1",\n "test": "tiny-rehearsal-phase1-contribution-v1",\n "relay_sha256": "%s",\n "mpc_ceremony_sha256": "%s"\n}' \ "$RELAY_SHA256" "$MPC_SHA256") [[ "$(<"$KIT_ROOT/compatibility.json")" == "$expected_compatibility" ]] || die "compatibility.json does not match the kit binaries" diff --git a/scripts/testdata/mpc-ceremony-compatibility-stub.sh b/scripts/testdata/mpc-ceremony-compatibility-stub.sh index 331b727..90592ee 100755 --- a/scripts/testdata/mpc-ceremony-compatibility-stub.sh +++ b/scripts/testdata/mpc-ceremony-compatibility-stub.sh @@ -5,6 +5,19 @@ set -euo pipefail ceremony_id=sha256:1111111111111111111111111111111111111111111111111111111111111111 +flag_value() { + local wanted=$1 + shift + while [[ $# -gt 0 ]]; do + if [[ $1 == "$wanted" && $# -ge 2 ]]; then + printf '%s\n' "$2" + return 0 + fi + shift + done + return 1 +} + if [[ ${1:-} == rehearsal && ${2:-} == init ]]; then out_dir= shift 2 @@ -16,13 +29,18 @@ if [[ ${1:-} == rehearsal && ${2:-} == init ]]; then esac done [[ -n "$out_dir" && ! -e "$out_dir" ]] || exit 1 - mkdir -p "$out_dir/public" "$out_dir/config" "$out_dir/keys" + mkdir -p "$out_dir/public/phase1" "$out_dir/config" "$out_dir/keys" printf '{}\n' >"$out_dir/public/ceremony.json" printf 'fixture\n' >"$out_dir/public/ceremony.sig" printf 'fixture\n' >"$out_dir/public/coordinator-public-key.hex" + printf '{}\n' >"$out_dir/public/phase1/chain-0000.json" + printf 'fixture\n' >"$out_dir/public/phase1/chain-0000.sig" printf '{}\n' >"$out_dir/config/environment.json" + printf 'fixture\n' >"$out_dir/keys/coordinator.ed25519.private.hex" printf 'fixture\n' >"$out_dir/keys/participant-01.ed25519.private.hex" - chmod 0600 "$out_dir/keys/participant-01.ed25519.private.hex" + chmod 0600 \ + "$out_dir/keys/coordinator.ed25519.private.hex" \ + "$out_dir/keys/participant-01.ed25519.private.hex" exit 0 fi @@ -36,11 +54,48 @@ if [[ ${1:-} == --format && ${2:-} == json && ${3:-} == inspect ]]; then printf '{"schema":"proof-tool-mpc-command-result-v1","ok":true,"command":"inspect participant","ceremony_id":"%s","participant_inspection":{"schema":"proof-tool-mpc-participant-inspection-v1","ceremony_id":"%s","participant_id":"participant-01","key_id":"participant-01-key","public_key_fingerprint":"sha256:3333333333333333333333333333333333333333333333333333333333333333","phase1_position":1,"phase2_position":1}}\n' \ "$ceremony_id" "$ceremony_id" ;; + chain) + chain=$(flag_value --chain "${@:5}") || exit 2 + if [[ $chain == *phase1/chain-0001.json ]]; then + printf '{"schema":"proof-tool-mpc-command-result-v1","ok":true,"command":"inspect chain","ceremony_id":"%s","phase":"phase1","chain_inspection":{"schema":"proof-tool-mpc-chain-inspection-v1","ceremony_id":"%s","phase":"phase1","accepted_count":1,"artifacts":[],"records":[{"index":1,"record_id":"fixture-record-01","participant_id":"participant-01","artifacts":[]}]}}\n' \ + "$ceremony_id" "$ceremony_id" + else + printf '{"schema":"proof-tool-mpc-command-result-v1","ok":true,"command":"inspect chain","ceremony_id":"%s","phase":"phase1","chain_inspection":{"schema":"proof-tool-mpc-chain-inspection-v1","ceremony_id":"%s","phase":"phase1","accepted_count":0,"artifacts":[],"records":[]}}\n' \ + "$ceremony_id" "$ceremony_id" + fi + ;; *) exit 2 ;; esac exit 0 fi +if [[ ${1:-} == phase1 && ${2:-} == contribute ]]; then + out_dir=$(flag_value --out-dir "$@") || exit 2 + [[ ! -e $out_dir ]] || exit 1 + mkdir -p "$out_dir" + printf 'fixture contribution\n' >"$out_dir/contribution.bin" + printf '{}\n' >"$out_dir/attestation.json" + printf 'fixture\n' >"$out_dir/attestation.sig" + exit 0 +fi + +if [[ ${1:-} == phase1 && ${2:-} == attest-erasure ]]; then + candidate_dir=$(flag_value --candidate-dir "$@") || exit 2 + [[ -d $candidate_dir ]] || exit 1 + printf '{}\n' >"$candidate_dir/erasure.json" + printf 'fixture\n' >"$candidate_dir/erasure.sig" + exit 0 +fi + +if [[ ${1:-} == phase1 && ${2:-} == verify ]]; then + transcript_dir=$(flag_value --transcript-dir "$@") || exit 2 + candidate_dir=$(flag_value --candidate-dir "$@") || exit 2 + [[ -d $candidate_dir && ! -e $transcript_dir/phase1/chain-0001.json ]] || exit 1 + printf '{}\n' >"$transcript_dir/phase1/chain-0001.json" + printf 'fixture\n' >"$transcript_dir/phase1/chain-0001.sig" + exit 0 +fi + if [[ ${1:-} == help || ${1:-} == --help || ${1:-} == -h ]]; then printf 'mpc-ceremony compatibility fixture\n' exit 0 diff --git a/scripts/verify-ceremony-kit-compatibility.sh b/scripts/verify-ceremony-kit-compatibility.sh index 34e15b2..d02d82f 100755 --- a/scripts/verify-ceremony-kit-compatibility.sh +++ b/scripts/verify-ceremony-kit-compatibility.sh @@ -79,7 +79,10 @@ for path in \ "$rehearsal/public/ceremony.json" \ "$rehearsal/public/ceremony.sig" \ "$rehearsal/public/coordinator-public-key.hex" \ + "$rehearsal/public/phase1/chain-0000.json" \ + "$rehearsal/public/phase1/chain-0000.sig" \ "$rehearsal/config/environment.json" \ + "$rehearsal/keys/coordinator.ed25519.private.hex" \ "$rehearsal/keys/participant-01.ed25519.private.hex"; do [[ -f "$path" && ! -L "$path" ]] || die "rehearsal initializer output is absent or unsafe: $path" done @@ -129,9 +132,13 @@ for phase in phase1 phase2; do die "Relay emitted an unexpected participant phase" done +HOME="$work_root/home" "$relay" advanced verify-ceremony-pair \ + --home "$rehearsal" \ + --ceremony-binary "$mpc" >/dev/null + ( set -o noclobber - printf '{\n "schema": "ceremony-kit-compatibility-v1",\n "test": "tiny-rehearsal-participant-config-v1",\n "relay_sha256": "%s",\n "mpc_ceremony_sha256": "%s"\n}\n' \ + printf '{\n "schema": "ceremony-kit-compatibility-v1",\n "test": "tiny-rehearsal-phase1-contribution-v1",\n "relay_sha256": "%s",\n "mpc_ceremony_sha256": "%s"\n}\n' \ "$RELAY_SHA256" "$MPC_SHA256" >"$EVIDENCE_OUT" ) chmod 0444 "$EVIDENCE_OUT"