Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ZIP ローカル検証(Ubuntu)

この手順は、検証ページでダウンロードした bundle.zip を対象に、Ubuntu 上で第三者が行える最小監査のガイドです。

0. 前提

  • 検証ページから bundle.zip をダウンロード済みであること
  • Ubuntu 22.04 / 24.04
  • このリポジトリ(stark-ballot-simulator)のソースを取得済みであること

リポジトリ非公開に関する注意 本リポジトリは現時点で非公開のため、ソースコードの取得はできません。 Step 3(bundle.zip を展開)と Step 6(journal.json の完全性チェック)はソースコード不要で実行できます。それ以外のステップはリポジトリ公開後に実行可能になります。

  • (Step 7-8 を実行する場合)Node.js 22+ と pnpm が利用可能であること
  • (Step 7-8 を実行する場合)$REPO_ROOTpnpm i を実行済みであること

検証ページのダウンロードは、s3BundleUrlverificationBundleUrl の候補から実行されます。S3 URL が期限切れの場合は refreshS3=1 で再取得されます。ここで扱う bundle.zippublic-input.jsonpublic は、「秘密データを含まない配布対象」という意味です。用語と取得経路は バンドル構造 を参照してください。

以降の手順では、リポジトリルートを REPO_ROOT として扱います。実際のクローン先に合わせて先に設定してください。

export REPO_ROOT="$HOME/stark-ballot-simulator"
export AUDIT_ROOT="$HOME/stark-audit"
cd "$REPO_ROOT"

1. Ubuntu セットアップ(Rust)

sudo apt update
sudo apt install -y build-essential pkg-config libssl-dev unzip jq curl ca-certificates

curl https://sh.rustup.rs -sSf | sh -s -- -y
source "$HOME/.cargo/env"

RUST_CHANNEL="$(awk -F'\"' '/^channel *=/ {print $2}' "$REPO_ROOT/rust-toolchain.toml")"
rustup toolchain install "$RUST_CHANNEL"
rustup default "$RUST_CHANNEL"
echo "rust_channel=$RUST_CHANNEL"

rustc --version
cargo --version

2. verifier-service をビルド

cd "$REPO_ROOT/verifier-service"
cargo build --release

生成物:

  • verifier-service/target/release/verifier-service

3. bundle.zip を展開

mkdir -p "$AUDIT_ROOT"
cp ~/Downloads/stark-ballot-verification-*.zip "$AUDIT_ROOT/bundle.zip"
cd "$AUDIT_ROOT"

unzip -o bundle.zip -d bundle
ls -1 bundle

最低限、以下のファイルが必要です。

  • bundle/receipt.json
  • bundle/journal.json
  • bundle/public-input.json
  • bundle/election-manifest.json
  • bundle/close-statement.json

metadata.json は同期モードでのみ含まれる場合があります。

4. 期待 Image ID を決定

journal.jsonmethodVersionreceipt.jsonimage_id を使って、public/imageId-mapping.json 上の候補から期待 Image ID を選びます。

METHOD_VERSION="$(jq -r '.methodVersion' bundle/journal.json)"
RECEIPT_IMAGE_ID="$(jq -r '.image_id // .imageId // .receipt.image_id // .receipt.imageId // empty' bundle/receipt.json | tr '[:upper:]' '[:lower:]')"

ARM_IMAGE_ID="$(jq -r --arg v "$METHOD_VERSION" '.mappings[$v].expectedImageID // empty' "$REPO_ROOT/public/imageId-mapping.json" | tr '[:upper:]' '[:lower:]')"
X86_IMAGE_ID="$(jq -r --arg v "$METHOD_VERSION" '.mappings[$v].expectedImageID_x86_64 // empty' "$REPO_ROOT/public/imageId-mapping.json" | tr '[:upper:]' '[:lower:]')"

case "$RECEIPT_IMAGE_ID" in
  "$ARM_IMAGE_ID")
    EXPECTED_IMAGE_ID="$ARM_IMAGE_ID"
    ;;
  "$X86_IMAGE_ID")
    EXPECTED_IMAGE_ID="$X86_IMAGE_ID"
    ;;
  "")
    echo "receipt_image_id is missing; choose the expected Image ID manually"
    exit 1
    ;;
  *)
    echo "receipt_image_id is not present in imageId-mapping.json for methodVersion=$METHOD_VERSION"
    exit 1
    ;;
esac

echo "methodVersion=$METHOD_VERSION"
echo "receiptImageId=$RECEIPT_IMAGE_ID"
echo "expectedImageId=$EXPECTED_IMAGE_ID"

既知の本番 bundle であることが分かっている場合は、通常 expectedImageID(ARM64 側)が選ばれます。ローカル x86_64 で生成した receipt を監査する場合は、expectedImageID_x86_64 が選ばれることがあります。

5. STARK レシートを検証

"$REPO_ROOT/verifier-service/target/release/verifier-service" verify \
  --bundle ./bundle.zip \
  --image-id "$EXPECTED_IMAGE_ID" \
  --output ./verification.json

echo "exit_code=$?"
jq '{status, expected_image_id, receipt_image_id, dev_mode_receipt, errors}' ./verification.json

判定:

  • exit_code=0 かつ status="success": 合格
  • exit_code=2 または status="dev_mode": フェイクレシート(本番検証としては不合格)
  • exit_code=3 または status="failed": 不合格

6. journal.json の完全性チェック

jq '{excludedSlots, missingSlots, invalidPresentedSlots, rejectedRecords, totalExpected, treeSize, totalVotes, validVotes, verifiedTally}' bundle/journal.json

jq -e '.excludedSlots == 0 and .missingSlots == 0 and .invalidPresentedSlots == 0' bundle/journal.json >/dev/null \
  && echo 'integrity_counts=ok' \
  || echo 'integrity_counts=ng'

jq -e '.totalExpected == .treeSize' bundle/journal.json >/dev/null \
  && echo 'expected_vs_tree=ok' \
  || echo 'expected_vs_tree=ng'

jq -e '(.verifiedTally | add) == .validVotes' bundle/journal.json >/dev/null \
  && echo 'tally_sum=ok' \
  || echo 'tally_sum=ng'

excludedSlots > 0 または missingSlots > 0 または invalidPresentedSlots > 0 は、検証失敗として扱います。加えて totalExpected != treeSize も、現行の必須チェックでは検証失敗です。

7. 公開監査アーティファクトの整合性チェック

election-manifest.jsonclose-statement.json は、現行の公開バンドルに含まれる Counted 段階の必須チェック用アーティファクトです。ここでは次を確認します。

  • election-manifest.jsonelectionConfigHash を再計算し、manifest 自身の宣言値と一致すること
  • manifest の electionId / electionConfigHashpublic-input.json / journal.json と矛盾しないこと
  • close-statement.json から sthDigest を再計算し、宣言された snapshot と一致すること
  • close statement の logId / treeSize / timestamp / bulletinRoot / sthDigestpublic-input.json / journal.json と矛盾しないこと
cd "$REPO_ROOT"

pnpm tsx -e "
import fs from 'node:fs';
import { buildCloseStatement, recomputeElectionManifestHash } from './src/lib/verification/public-audit-artifacts';

const [manifestPath, closePath, journalPath, publicInputPath] = process.argv.slice(1);
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
const closeStatement = JSON.parse(fs.readFileSync(closePath, 'utf-8'));
const journal = JSON.parse(fs.readFileSync(journalPath, 'utf-8'));
const publicInput = JSON.parse(fs.readFileSync(publicInputPath, 'utf-8'));

const normalize = (value) => String(value).toLowerCase();
const sameHex = (left, right) => normalize(left) === normalize(right);

const recomputedManifestHash = recomputeElectionManifestHash(manifest);
const rebuiltCloseStatement = buildCloseStatement({
  logId: closeStatement.logId,
  treeSize: closeStatement.treeSize,
  timestamp: closeStatement.timestamp,
  bulletinRoot: closeStatement.bulletinRoot,
});

const checks = {
  manifest_hash_ok: sameHex(recomputedManifestHash, manifest.electionConfigHash),
  manifest_election_id_ok:
    String(manifest.electionId) === String(publicInput.electionId) &&
    String(manifest.electionId) === String(journal.electionId),
  manifest_config_hash_ok:
    sameHex(manifest.electionConfigHash, publicInput.electionConfigHash) &&
    sameHex(manifest.electionConfigHash, journal.electionConfigHash),
  close_digest_ok: sameHex(rebuiltCloseStatement.sthDigest, closeStatement.sthDigest),
  close_timestamp_ok: closeStatement.timestamp === publicInput.timestamp,
  close_log_id_ok: sameHex(closeStatement.logId, publicInput.logId),
  close_tree_size_ok: closeStatement.treeSize === publicInput.treeSize && closeStatement.treeSize === journal.treeSize,
  close_bulletin_root_ok:
    sameHex(closeStatement.bulletinRoot, publicInput.bulletinRoot) &&
    sameHex(closeStatement.bulletinRoot, journal.bulletinRoot),
  close_sth_digest_ok: sameHex(closeStatement.sthDigest, journal.sthDigest),
};

console.log(JSON.stringify(checks, null, 2));
process.exit(Object.values(checks).every(Boolean) ? 0 : 1);
" \
  "$AUDIT_ROOT/bundle/election-manifest.json" \
  "$AUDIT_ROOT/bundle/close-statement.json" \
  "$AUDIT_ROOT/bundle/journal.json" \
  "$AUDIT_ROOT/bundle/public-input.json"

echo "exit_code=$?"

判定:

  • exit_code=0 かつ全項目が true: 合格
  • いずれかが false: counted_election_manifest_consistent または counted_close_statement_consistent 相当の失敗

8. inputCommitment 再計算

public-input.json から再計算した値が journal.jsoninputCommitment と一致することを確認します。 このステップには Node.js / pnpm と、$REPO_ROOT での pnpm i が必要です。

RECALC="$(cd "$REPO_ROOT" && pnpm tsx -e "import fs from 'node:fs'; import { computeInputCommitmentFromPublicInput } from './src/lib/zkvm/types'; const p = JSON.parse(fs.readFileSync(process.argv[1], 'utf-8')); console.log(computeInputCommitmentFromPublicInput(p));" "$AUDIT_ROOT/bundle/public-input.json")"
JOURNAL_COMMITMENT="$(jq -r '.inputCommitment' "$AUDIT_ROOT/bundle/journal.json")"

echo "recalculated=$RECALC"
echo "journal=$JOURNAL_COMMITMENT"

[ "${RECALC,,}" = "${JOURNAL_COMMITMENT,,}" ] && echo 'input_commitment=ok' || echo 'input_commitment=ng'

合格条件(bundle.zip 単体で確認できる最小セット)

  • verifier-service の結果が success
  • excludedSlots == 0 かつ missingSlots == 0 かつ invalidPresentedSlots == 0
  • totalExpected == treeSize
  • election-manifest.jsonclose-statement.json の整合チェックがすべて通る
  • inputCommitment の再計算値が journal.json と一致する

この手順のどれかが失敗した場合、Counted / STARK 段階の必須チェックを満たしていないため、Verified にはなりません。

一方で、この手順だけで /verify の最終判定を完全再現するわけではありません。bundle.zip 単体ではそろわない検証材料については 第三者検証ガイド の冒頭を参照してください。

この手順の対象範囲は 第三者検証ガイド を参照してください。