diff --git a/app/igneum-app/src/bin/ota-sign.rs b/app/igneum-app/src/bin/ota-sign.rs index ef444e1ec..df8dd5a8c 100644 --- a/app/igneum-app/src/bin/ota-sign.rs +++ b/app/igneum-app/src/bin/ota-sign.rs @@ -162,6 +162,18 @@ fn main() { Err(e) => die(&e), } } + // the project's GPU prover server manifest (src/proverserver.rs): parsed first, so a manifest that does not + // name the server, its sha256 and its size is never signed + Some("sign-server") if args.len() == 3 => { + let seed = manifest::hex_decode(&read_key_arg(&args[1])).unwrap_or_else(|| die("private key is not hex")); + let seed: [u8; 32] = seed.try_into().unwrap_or_else(|_| die("private key is not 32 bytes")); + let sk = SigningKey::from_bytes(&seed); + let bytes = std::fs::read(&args[2]).unwrap_or_else(|e| die(&format!("{}: {e}", args[2]))); + let text = std::str::from_utf8(&bytes).unwrap_or_else(|_| die("prover-server manifest is not UTF-8")); + let m = proverserver::parse(text).unwrap_or_else(|e| die(&format!("refusing to sign: {e}"))); + eprintln!("signing the prover server manifest: sp1-gpu-server {} bytes, sha256 {}, SP1 {} ({}), targets {}, built {} by {}", m.bytes, m.sha256, m.sp1_version, &m.sp1_commit[..12.min(m.sp1_commit.len())], m.cuda_archs, m.built_at, m.built_by); + println!("{}", manifest::hex_encode(&sk.sign(&bytes).to_bytes())); + } Some("sign-inputs") if args.len() == 3 => { let seed = manifest::hex_decode(&read_key_arg(&args[1])).unwrap_or_else(|| die("private key is not hex")); let seed: [u8; 32] = seed.try_into().unwrap_or_else(|_| die("private key is not 32 bytes")); @@ -209,7 +221,7 @@ fn main() { println!("ok: inputs built {} from node commit {} ({}); checked: {}", m.built_at, m.node_source_commit, m.node_source_branch, checked.join(", ")); } _ => { - eprintln!("usage: igneum-ota-sign keygen | sign | verify | embedded | fingerprint | sha256 | sign-jobs | verify-jobs | envelope-jobs | verify-signed-jobs | sign-inputs | verify-inputs [--zip z] [--dir d] [--node-commit c] | verify-server [--binary f]"); + eprintln!("usage: igneum-ota-sign keygen | sign | verify | embedded | fingerprint | sha256 | sign-jobs | verify-jobs | envelope-jobs | verify-signed-jobs | sign-inputs | verify-inputs [--zip z] [--dir d] [--node-commit c] | sign-server | verify-server [--binary f]"); std::process::exit(2); } } diff --git a/packaging/prover/push-server.sh b/packaging/prover/push-server.sh index 9ab861969..25d15a451 100755 --- a/packaging/prover/push-server.sh +++ b/packaging/prover/push-server.sh @@ -49,7 +49,7 @@ m = {"format": "igneum-prover-server/1", "sp1_version": b["sp1_version"], "sp1_c "files": {"sp1-gpu-server": {"sha256": sys.argv[4], "bytes": int(sys.argv[5])}}} open(sys.argv[2], "w").write(json.dumps(m, sort_keys=True, separators=(",", ":")) + "\n") PY -"$SIGNER" sign "$KEY" "$DEST/prover-server.json" > "$DEST/prover-server.json.sig" +"$SIGNER" sign-server "$KEY" "$DEST/prover-server.json" > "$DEST/prover-server.json.sig" cp "$BIN" "$DEST/sp1-gpu-server" "$SIGNER" verify-server "$PUB" "$DEST/prover-server.json" "$DEST/prover-server.json.sig" --binary "$DEST/sp1-gpu-server" "$SIGNER" verify-server embedded "$DEST/prover-server.json" "$DEST/prover-server.json.sig" >/dev/null || { echo "the embedded key does not verify this signature: the OTA key on this Mac is not the one the apps carry" >&2; exit 1; } diff --git a/tools/prover-floor/pc2-packaged-verify.ps1 b/tools/prover-floor/pc2-packaged-verify.ps1 new file mode 100644 index 000000000..ac1af3ea0 --- /dev/null +++ b/tools/prover-floor/pc2-packaged-verify.ps1 @@ -0,0 +1,89 @@ +# Prover floor, the packaging row's verification run (6 October 2026): the runtime half of the shipped-server path on +# PC 2, with what exists there today (the v4 server /opt/igneum-floor/bin/sp1-gpu-server 68b2f512 built on 6 October, +# the pv1 host). The signed manifest (prover-server.json + .sig, signed on the Mac with the OTA key) comes from the +# fetch job floor-pc2-manifest (jobs\floor-pc2-manifest\). Steps, each a RESULT line: (1) the manifest's sha256 and size +# against the binary (the app's check_binary, done here in bash); (2) the app's install script, byte for byte +# (app/igneum-app/src/proverserver.rs install_script), run as the app's WSL user: the server lands at +# ~/.sp1/bin/sp1-gpu-server where the SDK looks; (3) one v1 shard through the pv1 host with the 12 GB profile +# (SP1_GPU_ELEMENT_THRESHOLD=67108864) and the DEFAULT HOME, so the SDK spawns the installed server (the peak and the +# time, every proof verified); (4) the app's restore script, so the live prover is as it was (the stock server comes +# back by the SDK's download at its next shard). The app's prover is switched off for the run and on at the end; the +# miners are stopped by the job (--stop-miners); the live /opt/igneum host is never touched. +$ErrorActionPreference = 'Continue' +$urlFile = if ($env:IGNEUM_APP_DIR) { Join-Path $env:IGNEUM_APP_DIR 'app.url' } else { Join-Path $env:LOCALAPPDATA 'igneum\app\app.url' } +if (-not (Test-Path $urlFile)) { $urlFile = Join-Path $env:LOCALAPPDATA 'igneum\app\app.url' } +$base = (Get-Content $urlFile -Raw).Trim().TrimEnd('/') +function Stamp { (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') } +function Prove($on) { try { (Invoke-RestMethod -Method Post -Uri "$base/api/prove" -ContentType 'application/json' -Body (@{on=$on} | ConvertTo-Json -Compress) -TimeoutSec 10) | ConvertTo-Json -Compress } catch { "error: $_" } } +"RESULT start $(Stamp) prover off: $(Prove $false)" +Start-Sleep -Seconds 30 +$jobs = Split-Path $env:IGNEUM_JOB_DIR +$kit = Join-Path $jobs 'floor-pc2-manifest' +$manifest = Join-Path $kit 'prover-server.json' +if (-not (Test-Path $manifest)) { "RESULT verify_failed no manifest at $manifest (publish the fetch job floor-pc2-manifest first)"; "RESULT end $(Stamp) prover on: $(Prove $true)"; exit 2 } +function WslPath($p) { $w = (& wsl.exe -d Ubuntu-24.04 -u root -- wslpath -a ($p -replace '\\', '/') 2>$null); if ($w) { ($w -replace "`0", '').Trim() } else { '/mnt/c' + ($p.Substring(2) -replace '\\', '/') } } +$manW = WslPath $manifest +$job = $env:IGNEUM_JOB_DIR; New-Item -ItemType Directory -Force -Path $job | Out-Null +$jobW = WslPath $job +# the WSL user the app runs the host as: the first non-root user of the distribution (the app's prover runs unelevated) +$bash = @' +set -uo pipefail +stamp() { date -u +%Y-%m-%dT%H:%M:%SZ; } +MAN='MAN_PLACEHOLDER'; JOB='JOB_PLACEHOLDER' +BIN=/opt/igneum-floor/bin/sp1-gpu-server; H=/opt/igneum-pv1/igneum-prove-host; FX=/root/igneum-prove-pv1/proving/fixtures/fees-v1-shards2.json +[ -x "$BIN" ] && [ -x "$H" ] && [ -f "$FX" ] || { echo "RESULT verify_failed missing: bin=$([ -x $BIN ] && echo ok || echo no) host=$([ -x $H ] && echo ok || echo no) fixture=$([ -f $FX ] && echo ok || echo no)"; exit 2; } +# (1) the manifest against the binary, as the app's check_binary does (size first, then the sha256) +WANT=$(python3 -c 'import json,sys; f=json.load(open(sys.argv[1]))["files"]["sp1-gpu-server"]; print(f["sha256"], f["bytes"])' "$MAN") +WANT_SHA=${WANT% *}; WANT_BYTES=${WANT#* } +GOT_SHA=$(sha256sum "$BIN" | cut -c1-64); GOT_BYTES=$(stat -c %s "$BIN") +if [ "$GOT_SHA" = "$WANT_SHA" ] && [ "$GOT_BYTES" = "$WANT_BYTES" ]; then echo "RESULT check_binary ok sha256=$GOT_SHA bytes=$GOT_BYTES"; else echo "RESULT check_binary FAILED got $GOT_SHA/$GOT_BYTES want $WANT_SHA/$WANT_BYTES"; exit 2; fi +# a tampered copy is refused by the same check (the known-failed case of the gate) +cp "$BIN" "$JOB/tampered"; printf 'x' >> "$JOB/tampered" +T_SHA=$(sha256sum "$JOB/tampered" | cut -c1-64); T_BYTES=$(stat -c %s "$JOB/tampered") +if [ "$T_SHA" = "$WANT_SHA" ] || [ "$T_BYTES" = "$WANT_BYTES" ]; then echo "RESULT tampered_check FAILED: the tampered copy passed"; exit 2; else echo "RESULT tampered_check ok: refused ($T_BYTES bytes is not the manifest's $WANT_BYTES)"; fi +rm -f "$JOB/tampered" +# (2) the app's install script, as src/proverserver.rs writes it (sha compared first, the server stopped, the copy into ~/.sp1/bin) +pkill -f sp1-gpu-server 2>/dev/null; rm -f /tmp/sp1-cuda-*.sock +BEFORE=$(sha256sum "$HOME/.sp1/bin/sp1-gpu-server" 2>/dev/null | cut -c1-64 || true); echo "RESULT before user=$(id -un) home=$HOME installed_sha=${BEFORE:-none}" +cat > "$JOB/install.sh" </dev/null | cut -c1-64)" +if [ "\$have" = $WANT_SHA ]; then echo "RESULT server $WANT_SHA kept"; exit 0; fi +pkill -f sp1-gpu-server 2>/dev/null; rm -f /tmp/sp1-cuda-*.sock +cp '$BIN' "\$T.new" && chmod +x "\$T.new" && mv -f "\$T.new" "\$T" || { echo "RESULT server $WANT_SHA install FAILED"; exit 1; } +got="\$(sha256sum "\$T" | cut -c1-64)" +if [ "\$got" = $WANT_SHA ]; then echo "RESULT server $WANT_SHA installed"; else echo "RESULT server \$got MISMATCH after the copy"; exit 1; fi +EOS +bash "$JOB/install.sh"; rc=$?; echo "RESULT install_exit $rc" +bash "$JOB/install.sh" | sed 's/^RESULT server/RESULT server_again/' # the second run keeps it (the sha matches) +echo "RESULT installed_version $($HOME/.sp1/bin/sp1-gpu-server --version 2>/dev/null)" +# (3) one v1 shard, the 12 GB profile, the DEFAULT HOME: the SDK spawns ~/.sp1/bin/sp1-gpu-server, which is now the shipped one +nvidia-smi --query-gpu=timestamp,memory.used --format=csv,noheader,nounits -l 1 > "$JOB/smi.csv" 2>/dev/null & SMI=$! +t0=$(date +%s) +SP1_PROVER=cuda RUST_LOG=off SP1_GPU_FLOOR_LOG=1 SP1_GPU_ELEMENT_THRESHOLD=67108864 $H "$FX" --mode compressed --shard 0 --out "$JOB/res.json" > "$JOB/prove.log" 2>&1; prc=$? +wall=$(( $(date +%s) - t0 )); pkill -f sp1-gpu-server 2>/dev/null; kill $SMI 2>/dev/null; sleep 1; rm -f /tmp/sp1-cuda-*.sock +peak=$(awk -F', *' '{ if ($2+0 > m) m=$2+0 } END { print m+0 }' "$JOB/smi.csv") +line=$(grep -E "^RESULT compressed shard" "$JOB/prove.log" | tail -1 | sed -E 's/.*prove ([0-9.]+) s, proof ([0-9]+) bytes, verify ([0-9.]+) s, ([A-Z ]+);.*/prove_s=\1 bytes=\2 verify_s=\3 \4/') +opts=$(grep -E "^FLOOR opts" "$JOB/prove.log" | head -1 | cut -c1-160) +echo "RESULT prove profile=12gb threshold=67108864 peak_mib=$peak wall_s=$wall ${line:-no_result} exit=$prc server_kind=patched server_sha256=$WANT_SHA" +echo "RESULT prove_opts ${opts:-none}" +grep -iE "error|panick|Could not" "$JOB/prove.log" | grep -v "^FLOOR" | head -2 | sed 's/^/RESULT prove_err /' +# (4) the app's restore script: the stock server comes back by the SDK's own download at the live prover's next shard +cat > "$JOB/restore.sh" <<'EOS' +set -u +T="$HOME/.sp1/bin/sp1-gpu-server" +pkill -f sp1-gpu-server 2>/dev/null; rm -f /tmp/sp1-cuda-*.sock +rm -f "$T" +echo "RESULT server stock restored" +EOS +bash "$JOB/restore.sh" +echo "RESULT after installed_sha=$(sha256sum "$HOME/.sp1/bin/sp1-gpu-server" 2>/dev/null | cut -c1-64 || echo none) (none = the SDK downloads the stock 6.8.1 at the next proof)" +echo "RESULT verify_end $(stamp)" +'@ +$bash = $bash.Replace('MAN_PLACEHOLDER', $manW).Replace('JOB_PLACEHOLDER', $jobW) +$bashFile = Join-Path $job 'verify.sh' +[IO.File]::WriteAllText($bashFile, ($bash -replace "`r`n", "`n"), (New-Object System.Text.UTF8Encoding $false)) +& wsl.exe -d Ubuntu-24.04 -u root -- bash (WslPath $bashFile) 2>&1 | ForEach-Object { ($_ -replace "`0", '') } +"RESULT end $(Stamp) prover on: $(Prove $true)"