|
| 1 | +#!/usr/bin/env bash |
| 2 | +set -euo pipefail |
| 3 | + |
| 4 | +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| 5 | +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" |
| 6 | + |
| 7 | +# Colors |
| 8 | +RED='\033[0;31m' |
| 9 | +GREEN='\033[0;32m' |
| 10 | +YELLOW='\033[1;33m' |
| 11 | +NC='\033[0m' |
| 12 | + |
| 13 | +log_info() { echo -e "${GREEN}[INFO]${NC} $*"; } |
| 14 | +log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } |
| 15 | +log_error() { echo -e "${RED}[ERROR]${NC} $*"; } |
| 16 | + |
| 17 | +# --------------------------------------------------------------------------- |
| 18 | +# Defaults |
| 19 | +# --------------------------------------------------------------------------- |
| 20 | +CONFIG_FILE="${PROJECT_ROOT}/configs/mainnet.json" |
| 21 | +ASSETS_DIR="${PROJECT_ROOT}/../../coordinator/build/bin/assets_v2" |
| 22 | +DB_BUNDLE_INDEX="17302" |
| 23 | + |
| 24 | +deploy_plonk=true |
| 25 | +extract_digests=true |
| 26 | +deploy_wrapper=true |
| 27 | +register=true |
| 28 | + |
| 29 | +# --------------------------------------------------------------------------- |
| 30 | +# Parse args |
| 31 | +# --------------------------------------------------------------------------- |
| 32 | +while [[ $# -gt 0 ]]; do |
| 33 | + case "$1" in |
| 34 | + --config) |
| 35 | + CONFIG_FILE="$2"; shift 2 ;; |
| 36 | + --assets-dir) |
| 37 | + ASSETS_DIR="$2"; shift 2 ;; |
| 38 | + --bundle-index) |
| 39 | + DB_BUNDLE_INDEX="$2"; shift 2 ;; |
| 40 | + --skip-plonk) |
| 41 | + deploy_plonk=false; shift ;; |
| 42 | + --skip-wrapper) |
| 43 | + deploy_wrapper=false; shift ;; |
| 44 | + --skip-register) |
| 45 | + register=false; shift ;; |
| 46 | + --help|-h) |
| 47 | + cat << 'USAGE' |
| 48 | +Usage: 02-deploy-verifier.sh [options] |
| 49 | +
|
| 50 | +Deploy a new ZkEvmVerifierPostFeynman (with new plonk verifier + digests |
| 51 | +extracted from the DB proof) and register it on Anvil. |
| 52 | +
|
| 53 | +Options: |
| 54 | + --config <path> Config file (default: configs/mainnet.json) |
| 55 | + --assets-dir <path> Path to coordinator assets_v2/ (default: ../../coordinator/build/bin/assets_v2) |
| 56 | + --bundle-index <idx> Bundle index to extract digests from (default: 17302) |
| 57 | + --skip-plonk Skip deploying a new plonk verifier (reuse existing) |
| 58 | + --skip-wrapper Skip deploying the ZkEvmVerifierPostFeynman wrapper |
| 59 | + --skip-register Skip registering on MultipleVersionRollupVerifier |
| 60 | + -h, --help Show this help |
| 61 | +USAGE |
| 62 | + exit 0 ;; |
| 63 | + *) |
| 64 | + log_error "Unknown option: $1" |
| 65 | + exit 1 ;; |
| 66 | + esac |
| 67 | +done |
| 68 | + |
| 69 | +# --------------------------------------------------------------------------- |
| 70 | +# Load config |
| 71 | +# --------------------------------------------------------------------------- |
| 72 | +if [[ ! -f "$CONFIG_FILE" ]]; then |
| 73 | + log_error "Config file not found: $CONFIG_FILE" |
| 74 | + exit 1 |
| 75 | +fi |
| 76 | + |
| 77 | +ANVIL_RPC=$(jq -r '.fork.anvil_rpc // empty' "$CONFIG_FILE") |
| 78 | +SCROLL_CHAIN=$(jq -r '.contracts.scroll_chain // empty' "$CONFIG_FILE") |
| 79 | +MVRV=$(jq -r '.contracts.rollup_verifier // empty' "$CONFIG_FILE") |
| 80 | +OWNER=$(jq -r '.contracts.owner // empty' "$CONFIG_FILE") |
| 81 | +DB_DSN=$(jq -r '.db.dsn // empty' "$CONFIG_FILE") |
| 82 | +LAST_FINALIZED=$(jq -r '.reset.last_finalized_batch_index // empty' "$CONFIG_FILE") |
| 83 | + |
| 84 | +if [[ -z "$ANVIL_RPC" || -z "$MVRV" || -z "$OWNER" || -z "$DB_DSN" ]]; then |
| 85 | + log_error "Missing required fields in config" |
| 86 | + exit 1 |
| 87 | +fi |
| 88 | + |
| 89 | +# Compute start batch: must be >= lastFinalized + 1 AND >= existing latestVerifier startBatchIndex |
| 90 | +EXISTING_START=$(cast call "$MVRV" "latestVerifier(uint256)(uint64,address)" 10 --rpc-url "$ANVIL_RPC" 2>/dev/null | grep -oP '^\d+' || echo "0") |
| 91 | +MIN_START=$((LAST_FINALIZED + 1)) |
| 92 | +if [[ "$EXISTING_START" -gt "$MIN_START" ]]; then |
| 93 | + START_BATCH="$EXISTING_START" |
| 94 | +else |
| 95 | + START_BATCH="$MIN_START" |
| 96 | +fi |
| 97 | + |
| 98 | +log_info "Anvil RPC: $ANVIL_RPC" |
| 99 | +log_info "MVRV: $MVRV" |
| 100 | +log_info "Owner: $OWNER" |
| 101 | +log_info "Min start: $MIN_START" |
| 102 | +log_info "Existing start: $EXISTING_START" |
| 103 | +log_info "Using start: $START_BATCH" |
| 104 | +log_info "Bundle index: $DB_BUNDLE_INDEX" |
| 105 | + |
| 106 | +# --------------------------------------------------------------------------- |
| 107 | +# Pre-flight: unlock owner for all impersonated transactions |
| 108 | +# --------------------------------------------------------------------------- |
| 109 | +cast rpc anvil_impersonateAccount "$OWNER" --rpc-url "$ANVIL_RPC" >/dev/null 2>&1 || true |
| 110 | + |
| 111 | +# --------------------------------------------------------------------------- |
| 112 | +# 1. Deploy new Plonk Verifier from assets_v2/verifier.bin |
| 113 | +# --------------------------------------------------------------------------- |
| 114 | +PLONK_VERIFIER="" |
| 115 | +if $deploy_plonk; then |
| 116 | + VERIFIER_BIN="${ASSETS_DIR}/verifier.bin" |
| 117 | + if [[ ! -f "$VERIFIER_BIN" ]]; then |
| 118 | + log_error "Plonk verifier binary not found: $VERIFIER_BIN" |
| 119 | + log_error "Make sure coordinator assets are downloaded (run coordinator once or download from S3)." |
| 120 | + exit 1 |
| 121 | + fi |
| 122 | + |
| 123 | + PLONK_BYTECODE=$(xxd -p "$VERIFIER_BIN" | tr -d '\n') |
| 124 | + log_info "Deploying plonk verifier from $VERIFIER_BIN ($((${#PLONK_BYTECODE} / 2)) bytes) ..." |
| 125 | + |
| 126 | + # Anvil allows impersonation of any address with --unlocked |
| 127 | + PLONK_DEPLOY_OUTPUT=$(cast send --rpc-url "$ANVIL_RPC" --chain 1 \ |
| 128 | + --from "$OWNER" --unlocked --create "$PLONK_BYTECODE" 2>&1) |
| 129 | + |
| 130 | + # Extract deployed contract address from output |
| 131 | + PLONK_VERIFIER=$(echo "$PLONK_DEPLOY_OUTPUT" | grep -oP 'contractAddress\s+\K0x[a-fA-F0-9]{40}' || true) |
| 132 | + |
| 133 | + if [[ -z "$PLONK_VERIFIER" ]]; then |
| 134 | + log_error "Failed to extract plonk verifier address from cast output. Raw output:" |
| 135 | + echo "$PLONK_DEPLOY_OUTPUT" |
| 136 | + exit 1 |
| 137 | + fi |
| 138 | + |
| 139 | + log_info "Plonk verifier deployed at: $PLONK_VERIFIER" |
| 140 | +else |
| 141 | + # If skipping plonk deploy, read existing deployed_verifier and extract its plonkVerifier |
| 142 | + EXISTING_WRAPPER=$(jq -r '.contracts.deployed_verifier // empty' "$CONFIG_FILE") |
| 143 | + if [[ -n "$EXISTING_WRAPPER" && "$EXISTING_WRAPPER" != "null" ]]; then |
| 144 | + PLONK_VERIFIER=$(cast call "$EXISTING_WRAPPER" "plonkVerifier()(address)" --rpc-url "$ANVIL_RPC" 2>/dev/null || true) |
| 145 | + log_info "Reusing plonk verifier from existing wrapper: $PLONK_VERIFIER" |
| 146 | + fi |
| 147 | + if [[ -z "$PLONK_VERIFIER" ]]; then |
| 148 | + log_error "Cannot determine plonk verifier address. Either deploy one or provide an existing wrapper." |
| 149 | + exit 1 |
| 150 | + fi |
| 151 | +fi |
| 152 | + |
| 153 | +# --------------------------------------------------------------------------- |
| 154 | +# 2. Extract digests from DB proof instances |
| 155 | +# --------------------------------------------------------------------------- |
| 156 | +DIGEST1="" |
| 157 | +DIGEST2="" |
| 158 | +if $extract_digests; then |
| 159 | + log_info "Extracting digests from bundle $DB_BUNDLE_INDEX proof instances ..." |
| 160 | + |
| 161 | + PROOF_JSON=$(psql "$DB_DSN" -Atq -c " |
| 162 | + SELECT encode(proof, 'escape') |
| 163 | + FROM bundle |
| 164 | + WHERE index = $DB_BUNDLE_INDEX; |
| 165 | + " 2>/dev/null) |
| 166 | + |
| 167 | + if [[ -z "$PROOF_JSON" ]]; then |
| 168 | + log_error "Bundle $DB_BUNDLE_INDEX not found in DB" |
| 169 | + exit 1 |
| 170 | + fi |
| 171 | + |
| 172 | + # Parse instances base64 and extract digests |
| 173 | + DIGESTS=$(echo "$PROOF_JSON" | python3 -c " |
| 174 | +import sys, json, base64 |
| 175 | +data = sys.stdin.read() |
| 176 | +j = json.loads(data) |
| 177 | +instances_raw = base64.b64decode(j['proof']['instances']) |
| 178 | +# instances: 12 accumulators (384) + digest1 (32) + digest2 (32) + publicInputHash bytes (32*32=1024) |
| 179 | +digest1 = '0x' + instances_raw[384:416].hex() |
| 180 | +digest2 = '0x' + instances_raw[416:448].hex() |
| 181 | +print(digest1) |
| 182 | +print(digest2) |
| 183 | +") |
| 184 | + |
| 185 | + DIGEST1=$(echo "$DIGESTS" | sed -n '1p') |
| 186 | + DIGEST2=$(echo "$DIGESTS" | sed -n '2p') |
| 187 | + |
| 188 | + log_info "Extracted digest1: $DIGEST1" |
| 189 | + log_info "Extracted digest2: $DIGEST2" |
| 190 | +else |
| 191 | + log_error "--skip-digests not supported; digests must always be extracted from proof" |
| 192 | + exit 1 |
| 193 | +fi |
| 194 | + |
| 195 | +# --------------------------------------------------------------------------- |
| 196 | +# 3. Deploy ZkEvmVerifierPostFeynman wrapper |
| 197 | +# --------------------------------------------------------------------------- |
| 198 | +WRAPPER_ADDR="" |
| 199 | +if $deploy_wrapper; then |
| 200 | + # IMPORTANT: For new guest proofs (v0.8.0+), the correct wrapper is |
| 201 | + # ZkEvmVerifierPostFeynman, NOT ZkEvmVerifierPostEuclid. |
| 202 | + # PostFeynman computes keccak256(abi.encodePacked(protocolVersion, publicInput)) |
| 203 | + # which matches the bundle_pi_hash embedded in the proof instances. |
| 204 | + # protocolVersion = (domain << 6) + stf_version = (0 << 6) + 10 = 10 for Scroll+V10. |
| 205 | + PROTOCOL_VERSION=10 |
| 206 | + log_info "Deploying ZkEvmVerifierPostFeynman ..." |
| 207 | + log_info " plonkVerifier: $PLONK_VERIFIER" |
| 208 | + log_info " digest1: $DIGEST1" |
| 209 | + log_info " digest2: $DIGEST2" |
| 210 | + log_info " protocolVersion: $PROTOCOL_VERSION" |
| 211 | + |
| 212 | + cd "${PROJECT_ROOT}/../../scroll-contracts" |
| 213 | + |
| 214 | + WRAPPER_OUTPUT=$(forge create --broadcast --evm-version cancun --rpc-url "$ANVIL_RPC" \ |
| 215 | + --from "$OWNER" --unlocked \ |
| 216 | + src/libraries/verifier/ZkEvmVerifierPostFeynman.sol:ZkEvmVerifierPostFeynman \ |
| 217 | + --constructor-args "$PLONK_VERIFIER" "$DIGEST1" "$DIGEST2" "$PROTOCOL_VERSION" 2>&1) |
| 218 | + |
| 219 | + WRAPPER_ADDR=$(echo "$WRAPPER_OUTPUT" | grep -oP 'Deployed to:\s+\K0x[a-fA-F0-9]{40}' || true) |
| 220 | + |
| 221 | + if [[ -z "$WRAPPER_ADDR" ]]; then |
| 222 | + log_error "Failed to extract wrapper address from forge output. Raw output:" |
| 223 | + echo "$WRAPPER_OUTPUT" |
| 224 | + exit 1 |
| 225 | + fi |
| 226 | + |
| 227 | + log_info "ZkEvmVerifierPostFeynman deployed at: $WRAPPER_ADDR" |
| 228 | + |
| 229 | + # Verify on-chain |
| 230 | + ONCHAIN_DIGEST1=$(cast call "$WRAPPER_ADDR" "verifierDigest1()(bytes32)" --rpc-url "$ANVIL_RPC") |
| 231 | + ONCHAIN_DIGEST2=$(cast call "$WRAPPER_ADDR" "verifierDigest2()(bytes32)" --rpc-url "$ANVIL_RPC") |
| 232 | + ONCHAIN_PLONK=$(cast call "$WRAPPER_ADDR" "plonkVerifier()(address)" --rpc-url "$ANVIL_RPC") |
| 233 | + |
| 234 | + ONCHAIN_PROTO=$(cast call "$WRAPPER_ADDR" "protocolVersion()(uint256)" --rpc-url "$ANVIL_RPC") |
| 235 | + log_info "On-chain verification:" |
| 236 | + log_info " plonkVerifier: $ONCHAIN_PLONK" |
| 237 | + log_info " digest1: $ONCHAIN_DIGEST1" |
| 238 | + log_info " digest2: $ONCHAIN_DIGEST2" |
| 239 | + log_info " protocolVersion: $ONCHAIN_PROTO" |
| 240 | +else |
| 241 | + WRAPPER_ADDR=$(jq -r '.contracts.deployed_verifier // empty' "$CONFIG_FILE") |
| 242 | + log_info "Reusing existing wrapper: $WRAPPER_ADDR" |
| 243 | +fi |
| 244 | + |
| 245 | +# --------------------------------------------------------------------------- |
| 246 | +# 4. Register on MultipleVersionRollupVerifier |
| 247 | +# --------------------------------------------------------------------------- |
| 248 | +if $register; then |
| 249 | + log_info "Registering verifier on MultipleVersionRollupVerifier ..." |
| 250 | + log_info " version: 10" |
| 251 | + log_info " startBatch: $START_BATCH" |
| 252 | + log_info " verifier: $WRAPPER_ADDR" |
| 253 | + |
| 254 | + cast send "$MVRV" \ |
| 255 | + "updateVerifier(uint256,uint64,address)" \ |
| 256 | + 10 "$START_BATCH" "$WRAPPER_ADDR" \ |
| 257 | + --from "$OWNER" --rpc-url "$ANVIL_RPC" --unlocked |
| 258 | + |
| 259 | + # Verify registration |
| 260 | + REGISTERED=$(cast call "$MVRV" "getVerifier(uint256,uint256)(address)" 10 "$START_BATCH" --rpc-url "$ANVIL_RPC") |
| 261 | + log_info "getVerifier(10, $START_BATCH) = $REGISTERED" |
| 262 | + |
| 263 | + if [[ "${REGISTERED,,}" != "${WRAPPER_ADDR,,}" ]]; then |
| 264 | + log_error "Registration verification failed!" |
| 265 | + exit 1 |
| 266 | + fi |
| 267 | + |
| 268 | + log_info "Registration verified ✅" |
| 269 | +fi |
| 270 | + |
| 271 | +# --------------------------------------------------------------------------- |
| 272 | +# 5. Update config file |
| 273 | +# --------------------------------------------------------------------------- |
| 274 | +log_info "Updating config file: $CONFIG_FILE" |
| 275 | +tmp=$(mktemp) |
| 276 | +jq --arg addr "$WRAPPER_ADDR" '.contracts.deployed_verifier = $addr' "$CONFIG_FILE" > "$tmp" && mv "$tmp" "$CONFIG_FILE" |
| 277 | +log_info "Config updated with deployed_verifier = $WRAPPER_ADDR" |
| 278 | + |
| 279 | +log_info "Done! 🎉" |
| 280 | +log_info "" |
| 281 | +log_info "Summary:" |
| 282 | +log_info " Plonk Verifier: $PLONK_VERIFIER" |
| 283 | +log_info " Wrapper: $WRAPPER_ADDR" |
| 284 | +log_info " Digest1: $DIGEST1" |
| 285 | +log_info " Digest2: $DIGEST2" |
0 commit comments