Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1a906b0
refactor(l1): gate parallel paths on rayon only
iovoid Aug 3, 2026
af510e1
fix(l1): restore rayon default for ef_tests-state
iovoid Aug 3, 2026
565f9c9
fix(l1): fix doc-comment lint under rayon feature
iovoid Aug 3, 2026
a8c11e9
refactor(l1): drop vestigial eip-8025 features
iovoid Aug 3, 2026
2587d8c
refactor(l1): re-gate levm test mods on rayon
iovoid Aug 3, 2026
40aaa6e
test(l1): fill #3278 conformance vectors
iovoid Aug 4, 2026
ddead9e
test(l1): pin progressive SSZ vs remerkleable
iovoid Aug 4, 2026
270504d
test(l1): cover progressive container root parity
iovoid Aug 4, 2026
4ece074
feat(l1): adopt progressive SSZ + EIP-8282 requests
iovoid Aug 4, 2026
3f286da
feat(l1): drop wire ChainConfig, add schema_id
iovoid Aug 4, 2026
5d2c114
feat(l1): prefix EXECUTE input with schema id
iovoid Aug 4, 2026
8176e23
fix(l1): hoist public-key check, unconditional libssz
iovoid Aug 5, 2026
2d2b62b
refactor(l1): remove the eip-8025 feature flag
iovoid Aug 5, 2026
bf5a197
feat(l1): port stateless-validator runner crate
iovoid Aug 5, 2026
9624d70
ci(l1): publish stateless-validator ELFs and VKs
iovoid Aug 5, 2026
f412ef2
docs(l1): rewrite eip-8025 for the stateless guest
iovoid Aug 5, 2026
cbc72eb
feat(l2): populate stateless public_keys
iovoid Aug 5, 2026
2f4741f
ci: sign stateless-validator release assets
iovoid Aug 5, 2026
c9a4ade
ci: derive minisign pubkey from the secret key
iovoid Aug 5, 2026
6eec36f
ci: cross-check the minisign pubkey secret
iovoid Aug 5, 2026
1a6d094
ci: commit the minisign public key
iovoid Aug 5, 2026
42a4013
ci: add a release-asset signing dry run
iovoid Aug 5, 2026
3b251ea
ci: don't write GHCR cache on manual dispatch
iovoid Aug 6, 2026
373b1da
fix(guest): declare alloc for zkVM crypto providers
iovoid Aug 6, 2026
790d884
deps: libssz 0.3.0 with the progressive fix
iovoid Aug 6, 2026
08b09af
test(guest): report which output field diverged
iovoid Aug 6, 2026
fa675c7
ci: execute guest via ere-server twirp rpc
iovoid Aug 6, 2026
5c3af7e
ci: allow zisk output padding, widen openvm startup
iovoid Aug 6, 2026
ef7f375
fix(ci): write VK into the mounted output dir
iovoid Aug 6, 2026
4998260
fix(ci): v-prefix zkvm version in asset names
iovoid Aug 6, 2026
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
51 changes: 51 additions & 0 deletions .github/actions/sign-stateless/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Sign stateless-validator artifacts
description: >
Install minisign and sign the stateless-validator ELFs and verification keys
in a directory, as required by the zkEVM guest handbook.

Exists so the release job and the dry-run job cannot drift apart: a dry run
that signs differently from the real release is not testing the real release.

inputs:
artifact-dir:
description: Directory to scan for stateless-validator .elf/.vk files.
required: true
secret-key:
description: minisign private key (the MINISIGN_SECRET_KEY secret).
required: true
password:
description: Password for the private key. Empty for a `-W` key.
required: false
default: ""
public-key:
description: >
Recorded public key (the MINISIGN_PUBLIC_KEY secret). Optional, and
cross-checked against the key derived from the private key rather than
used in its place.
required: false
default: ""
trusted-comment-suffix:
description: >
Appended to each signed trusted comment, e.g. the tag and commit. Covered
by the signature, so it is a provenance claim rather than a label.
required: false
default: ""

runs:
using: composite
steps:
- name: Install minisign
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y minisign

- name: Sign
shell: bash
env:
MINISIGN_SECRET_KEY: ${{ inputs.secret-key }}
MINISIGN_PASSWORD: ${{ inputs.password }}
MINISIGN_PUBLIC_KEY: ${{ inputs.public-key }}
TRUSTED_COMMENT_SUFFIX: ${{ inputs.trusted-comment-suffix }}
ARTIFACT_DIR: ${{ inputs.artifact-dir }}
run: .github/scripts/sign-stateless-artifacts.sh "$ARTIFACT_DIR"
2 changes: 2 additions & 0 deletions .github/minisign.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
untrusted comment: minisign public key E074BFD13AAB8A02
RWQCiqs60b904PZll0gXEAbQlLwdt7MXuitSIt2425a59ULS0NHpArDL
124 changes: 124 additions & 0 deletions .github/scripts/ere-execute.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#!/usr/bin/env python3
"""Execute an ELF on a running `ere-server` and write its public values.

`ere-server` has no `execute` subcommand — execution is a Twirp RPC
(`/twirp/api.ZkvmService/Execute`) served while the process runs in server mode.
See `crates/server/api/proto/api.proto` in eth-act/ere:

message ExecuteRequest { bytes input_stdin = 1; optional bytes input_proofs = 2; }
message ExecuteResponse { oneof result { ExecuteOk ok = 1; string err = 2; } }
message ExecuteOk { bytes public_values = 1; bytes report = 2; }

Speaks protobuf rather than Twirp's JSON on purpose. The generated types carry a
bare `#[derive(serde::Serialize)]` with no `rename_all` and no base64 helper, so
JSON would encode `bytes` as an array of integers — for a multi-megabyte witness
that is both enormous and needless. The two messages here are small enough to
encode and decode by hand, which also removes any guesswork about field naming.

Usage: ere-execute.py <url> <input-file> <output-file>
"""

import sys
import urllib.error
import urllib.request


def encode_varint(value: int) -> bytes:
out = bytearray()
while True:
byte = value & 0x7F
value >>= 7
out.append(byte | (0x80 if value else 0))
if not value:
return bytes(out)


def decode_varint(buf: bytes, pos: int) -> tuple[int, int]:
value = shift = 0
while True:
if pos >= len(buf):
raise ValueError("truncated varint")
byte = buf[pos]
pos += 1
value |= (byte & 0x7F) << shift
if not byte & 0x80:
return value, pos
shift += 7
if shift > 63:
raise ValueError("varint too long")


def fields(buf: bytes):
"""Yield (field_number, wire_type, payload) for a protobuf message."""
pos = 0
while pos < len(buf):
key, pos = decode_varint(buf, pos)
field, wire = key >> 3, key & 0x07
if wire == 2: # length-delimited
length, pos = decode_varint(buf, pos)
yield field, wire, buf[pos : pos + length]
pos += length
elif wire == 0: # varint
value, pos = decode_varint(buf, pos)
yield field, wire, value
elif wire == 5:
yield field, wire, buf[pos : pos + 4]
pos += 4
elif wire == 1:
yield field, wire, buf[pos : pos + 8]
pos += 8
else:
raise ValueError(f"unsupported wire type {wire} for field {field}")


def encode_execute_request(stdin: bytes) -> bytes:
"""ExecuteRequest with only `input_stdin` (field 1, length-delimited)."""
return b"\x0a" + encode_varint(len(stdin)) + stdin


def decode_execute_response(body: bytes) -> bytes:
"""Return `ok.public_values`, or raise with the server's `err` string."""
for field, _wire, payload in fields(body):
if field == 1: # ExecuteOk
for inner_field, _w, inner in fields(payload):
if inner_field == 1: # public_values
return inner
raise ValueError("ExecuteOk carried no public_values")
if field == 2: # err
raise RuntimeError(f"guest execution failed: {payload.decode('utf-8', 'replace')}")
raise ValueError("ExecuteResponse set neither ok nor err")


def main() -> int:
if len(sys.argv) != 4:
print(__doc__, file=sys.stderr)
return 2
url, input_path, output_path = sys.argv[1], sys.argv[2], sys.argv[3]

with open(input_path, "rb") as handle:
stdin = handle.read()
print(f"executing with {len(stdin)} bytes of statelessInputBytes")

request = urllib.request.Request(
url,
data=encode_execute_request(stdin),
headers={"Content-Type": "application/protobuf"},
method="POST",
)
try:
with urllib.request.urlopen(request, timeout=1800) as response:
body = response.read()
except urllib.error.HTTPError as err:
detail = err.read().decode("utf-8", "replace")
print(f"ere-server returned HTTP {err.code}: {detail}", file=sys.stderr)
return 1

public_values = decode_execute_response(body)
with open(output_path, "wb") as handle:
handle.write(public_values)
print(f"wrote {len(public_values)} bytes of statelessOutputBytes to {output_path}")
return 0


if __name__ == "__main__":
sys.exit(main())
62 changes: 62 additions & 0 deletions .github/scripts/extract-stateless-fixture.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env bash
#
# Extracts one conformance vector's statelessInputBytes / statelessOutputBytes
# pair into raw binary, for the ere-server acceptance check in tag_release.yaml.
#
# Picks a TRUE-SUCCESS case (successful_validation == 1) deterministically. That
# is not a detail: the root, chain_id and schema_id are all computed before or
# without executing the block, so a guest whose execution is completely broken
# still reproduces a failure vector exactly. Only a success case proves the ELF
# can actually validate a block.
#
# Writes: output/stateless-input.bin, output/expected-output.bin

set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
VECTORS="$ROOT/tooling/ef_tests/blockchain/vectors_stateless_3278/blockchain_tests"
OUT="$ROOT/output"
mkdir -p "$OUT"

if [[ ! -d $VECTORS ]]; then
echo "No vectors at $VECTORS; run 'make -C tooling/ef_tests/blockchain stateless-vector' first" >&2
exit 1
fi

# `find | sort` for determinism, so a failure is reproducible rather than
# dependent on filesystem order. os-walk rather than a glob because the fill
# output nests fixtures under a dot-prefixed work directory.
FOUND=""
while IFS= read -r file; do
PAIR=$(jq -r '
to_entries[] | .value as $t
| ($t.blocks // [])[] as $b
| select(($b.statelessInputBytes // "") != "")
| select(($b.statelessOutputBytes // "") != "")
# byte 32 of the SSZ result is successful_validation; hex chars 64..66.
| select(($b.statelessOutputBytes | ltrimstr("0x") | .[64:66]) == "01")
| "\($b.statelessInputBytes)\t\($b.statelessOutputBytes)"
' "$file" 2>/dev/null | head -1 || true)
if [[ -n $PAIR ]]; then
FOUND="$file"
printf '%s' "${PAIR%%$'\t'*}" | sed 's/^0x//' | xxd -r -p > "$OUT/stateless-input.bin"
printf '%s' "${PAIR##*$'\t'}" | sed 's/^0x//' | xxd -r -p > "$OUT/expected-output.bin"
break
fi
done < <(find "$VECTORS" -name '*.json' | sort)

if [[ -z $FOUND ]]; then
echo "No true-success vector found under $VECTORS" >&2
echo "A vector set with no successful_validation==1 case cannot prove the ELF executes." >&2
exit 1
fi

# Belt and braces: assert what we wrote is a 43-byte success result.
LEN=$(wc -c < "$OUT/expected-output.bin" | tr -d ' ')
[[ $LEN -eq 43 ]] || { echo "expected output is $LEN bytes, want 43" >&2; exit 1; }
SUCCESS=$(xxd -p -s 32 -l 1 "$OUT/expected-output.bin")
[[ $SUCCESS == "01" ]] || { echo "expected output is not a success case ($SUCCESS)" >&2; exit 1; }

echo "Using fixture: ${FOUND#"$ROOT"/}"
echo " input: $(wc -c < "$OUT/stateless-input.bin" | tr -d ' ') bytes"
echo " output: $LEN bytes, successful_validation=1"
Loading
Loading