diff --git a/.github/scripts/aggregate_recursion_histogram.py b/.github/scripts/aggregate_recursion_histogram.py new file mode 100755 index 000000000..0be0a3010 --- /dev/null +++ b/.github/scripts/aggregate_recursion_histogram.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 +"""Format the recursion-guest per-function profile as a Markdown PR comment. + +`test_recursion_profile_1query`/`_multiquery` print a global top-25 functions +table (folded over all verifier steps, % of total run cycles), followed by +one top-25 table per verifier step (% of that step's own cycles, so the +table shows what dominates *within* the step) — e.g. how much of +`step4:openings` is `keccak`. We parse all of those tables and render them +as Markdown. + + Top 25 functions by cycle count (aggregated over their PCs, all steps; % of total cycles): + rank cycles % cum % PCs function + 1 5335072 24.95% 24.95% 72 <...>::visit_seq::<...> + + Top 25 functions by cycle count — step airs_bus_balance (% of this step's 5129138364 cycles): + rank cycles % cum % PCs function + 1 5335072 24.95% 24.95% 72 <...>::visit_seq::<...> + +Reads the test's captured output from argv[1]; writes the Markdown body to +argv[2] (or stdout). +""" + +import re +import sys +from collections import OrderedDict + +# A per-function summary row: rank, cycles, pct%, cum%, pcs, function. +FN_ROW = re.compile( + r"^\s*\d+\s+(\d+)\s+([\d.]+)%\s+([\d.]+)%\s+(\d+)\s+(.*\S)\s*$" +) +HEADER_ROW = re.compile(r"^\s*rank\s+cycles") +GLOBAL_TABLE_START = re.compile( + r"Top \d+ functions by cycle count \(aggregated over their PCs, all steps" +) +STEP_TABLE_START = re.compile( + r"Top \d+ functions by cycle count — step (\S+) \(% of this step's (\d+) cycles\):" +) +TOTAL_CYCLES = re.compile(r"Total cycles\s*:\s*(\d+)") +UNIQUE_PCS = re.compile(r"Unique PCs\s*:\s*(\d+)") +EXEC_TIME = re.compile(r"Exec time\s*:\s*(\S+)") + +GLOBAL_KEY = "__global__" + + +def parse(text): + total_cycles = unique_pcs = exec_time = None + # GLOBAL_KEY -> {"denom": int|None, "rows": [...]}, then one entry per + # step tag in first-seen order. + tables = OrderedDict() + current = None + skip_header = False + for line in text.splitlines(): + if total_cycles is None and (m := TOTAL_CYCLES.search(line)): + total_cycles = int(m.group(1)) + if unique_pcs is None and (m := UNIQUE_PCS.search(line)): + unique_pcs = int(m.group(1)) + if exec_time is None and (m := EXEC_TIME.search(line)): + exec_time = m.group(1) + + if GLOBAL_TABLE_START.search(line): + current = GLOBAL_KEY + tables[current] = {"denom": total_cycles, "rows": []} + skip_header = True + continue + if m := STEP_TABLE_START.search(line): + current = m.group(1) + tables[current] = {"denom": int(m.group(2)), "rows": []} + skip_header = True + continue + + if current is None: + continue + if skip_header: + # The header row right after a table-start line; anything else + # (e.g. a stray blank line) just ends the table early, which is + # fine — an empty table renders as "no rows". + skip_header = False + if HEADER_ROW.match(line): + continue + if m := FN_ROW.match(line): + tables[current]["rows"].append( + { + "cycles": int(m.group(1)), + "pct": m.group(2), + "cum": m.group(3), + "pcs": int(m.group(4)), + "fn": m.group(5), + } + ) + else: + current = None + + return total_cycles, unique_pcs, exec_time, tables + + +def short(name, width=90): + return name if len(name) <= width else name[: width - 1] + "…" + + +def render_table(rows, denom_label): + if not rows: + return "> _no rows_\n" + body = "| Rank | Cycles | % | Cum % | PCs | Function |\n" + body += "|-----:|-------:|--:|------:|----:|----------|\n" + for i, r in enumerate(rows, 1): + body += ( + f"| {i} | {r['cycles']:,} | {r['pct']}% | {r['cum']}% | " + f"{r['pcs']} | `{short(r['fn'])}` |\n" + ) + last_cum = rows[-1]["cum"] + body += ( + f"\nEach function's cycles are summed over all its program counters " + f"in this table's scope; the top {len(rows)} cover {last_cum}% of " + f"{denom_label}.\n" + ) + return body + + +def render(total_cycles, unique_pcs, exec_time, tables, title="Recursion guest profile"): + if not tables.get(GLOBAL_KEY, {}).get("rows"): + return ( + f"### {title}\n\n" + "> ⚠️ No per-function rows found in the test output — the run may " + "have failed before printing the table. Check the workflow logs.\n" + ) + + body = f"### {title}\n\n" + if total_cycles is not None: + body += f"**Total cycles:** {total_cycles:,}" + if unique_pcs is not None: + body += f" · **Unique PCs:** {unique_pcs:,}" + if exec_time: + body += f" · **Exec time:** {exec_time}" + body += "\n\n" + + global_rows = tables[GLOBAL_KEY]["rows"] + body += f"#### Top {len(global_rows)} functions by cycles (all steps)\n\n" + body += render_table(global_rows, "total cycles") + + for step, table in tables.items(): + if step == GLOBAL_KEY: + continue + rows, denom = table["rows"], table["denom"] + denom_note = f" of {denom:,} step cycles" if denom is not None else "" + body += ( + f"\n
Step {step}{denom_note} — " + f"top {len(rows)} functions\n\n" + ) + body += render_table(rows, "this step's cycles") + body += "\n
\n" + + return body + + +def main(): + import argparse + + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("log", help="captured test output to parse") + ap.add_argument("-o", "--out", help="write Markdown here instead of stdout") + ap.add_argument( + "-t", + "--title", + default="Recursion guest profile", + help="section heading (e.g. the test/config name)", + ) + args = ap.parse_args() + + with open(args.log, "r", errors="replace") as f: + text = f.read() + body = render(*parse(text), title=args.title) + if args.out: + with open(args.out, "w") as f: + f.write(body) + else: + sys.stdout.write(body) + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/profile-recursion.yml b/.github/workflows/profile-recursion.yml new file mode 100644 index 000000000..680741f15 --- /dev/null +++ b/.github/workflows/profile-recursion.yml @@ -0,0 +1,178 @@ +name: Profile Recursion (PR) + +# Runs the recursion-guest PC histogram diagnostics (single-query and +# multi-query, in parallel via a matrix) and posts a combined per-function +# profile as a PR comment. Triggered by a `/profile_recursion` comment from a +# repo member, or manually via workflow_dispatch. + +on: + workflow_dispatch: + issue_comment: + types: [created] + +permissions: + contents: read + pull-requests: write + +concurrency: + group: profile-recursion-${{ github.event.issue.number || github.run_id }} + cancel-in-progress: true + +jobs: + # One job per configuration; they run in parallel and each uploads a Markdown + # fragment artifact. The `comment` job stitches them into one PR comment. + profile: + # Skip unless: workflow_dispatch, or "/profile_recursion" comment on a PR by a member. + if: >- + github.event_name == 'workflow_dispatch' || + (github.event_name == 'issue_comment' && + github.event.issue.pull_request && + startsWith(github.event.comment.body, '/profile_recursion') && + contains(fromJSON('["MEMBER","OWNER","COLLABORATOR"]'), github.event.comment.author_association)) + runs-on: [self-hosted, bench] + timeout-minutes: 90 + strategy: + fail-fast: false + matrix: + include: + - name: single-query + test: single + title: "Single query (blowup=2, 1 query)" + - name: multi-query + test: multi + title: "Multi query (blowup=8, 128-bit)" + steps: + - name: React to comment + if: github.event_name == 'issue_comment' && matrix.name == 'single-query' + uses: actions/github-script@v7 + with: + script: | + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: context.payload.comment.id, + content: 'eyes' + }); + + - name: Get PR head ref + id: pr-ref + if: github.event_name == 'issue_comment' + env: + GH_TOKEN: ${{ github.token }} + PR_NUM: ${{ github.event.issue.number }} + run: | + SHA=$(gh pr view "$PR_NUM" --repo "$GITHUB_REPOSITORY" --json headRefOid -q .headRefOid) + echo "sha=$SHA" >> "$GITHUB_OUTPUT" + + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ steps.pr-ref.outputs.sha || github.sha }} + + - name: Setup Rust Environment + uses: ./.github/actions/setup-rust + + - name: Add cargo to PATH + run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" + + - name: Run recursion PC histogram (${{ matrix.name }}) + env: + TEST: ${{ matrix.test }} + run: | + # Self-provision the RISC-V sysroot in a user-writable dir (the default + # /opt path on the bench runner is root-owned); the guest ELF build the + # test triggers picks this up via the Makefile's `SYSROOT_DIR ?=`. + export SYSROOT_DIR="$HOME/.lambda-vm-sysroot" + set -o pipefail + make test-profile-recursion-$TEST 2>&1 | tee /tmp/hist.log + + - name: Aggregate into a per-function fragment + if: always() + env: + TITLE: ${{ matrix.title }} + run: | + python3 .github/scripts/aggregate_recursion_histogram.py \ + /tmp/hist.log --title "$TITLE" --out "/tmp/fragment-${{ matrix.name }}.md" + cat "/tmp/fragment-${{ matrix.name }}.md" >> "$GITHUB_STEP_SUMMARY" + + - name: Upload fragment + if: always() + uses: actions/upload-artifact@v4 + with: + name: profile-fragment-${{ matrix.name }} + path: /tmp/fragment-${{ matrix.name }}.md + retention-days: 7 + + # Stitch the matrix fragments into a single PR comment. + comment: + needs: profile + # always() so partial-matrix failures still post; skip when `profile` was + # skipped (non-/profile_recursion or non-member comment) so this job — and + # the self-hosted bench runner it spins up — doesn't fire on every comment. + if: always() && github.event_name == 'issue_comment' && needs.profile.result != 'skipped' + runs-on: ubuntu-latest + steps: + - name: Get PR head ref + id: pr-ref + env: + GH_TOKEN: ${{ github.token }} + PR_NUM: ${{ github.event.issue.number }} + run: | + SHA=$(gh pr view "$PR_NUM" --repo "$GITHUB_REPOSITORY" --json headRefOid -q .headRefOid) + echo "sha=$SHA" >> "$GITHUB_OUTPUT" + + - name: Download fragments + uses: actions/download-artifact@v4 + with: + path: fragments + pattern: profile-fragment-* + merge-multiple: true + + - name: Assemble comment body + env: + COMMIT_SHA: ${{ steps.pr-ref.outputs.sha }} + run: | + { + echo "## Recursion guest profile" + echo + # Single-query first, then multi-query, then any others. + for frag in fragments/fragment-single-query.md \ + fragments/fragment-multi-query.md; do + [ -f "$frag" ] && { cat "$frag"; echo; } + done + echo "Commit: ${COMMIT_SHA:0:8} · Runner: self-hosted bench" + } > /tmp/profile_comment.md + cat /tmp/profile_comment.md + + - name: Comment on PR + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const body = fs.readFileSync('/tmp/profile_comment.md', 'utf8'); + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + // Reuse our own marker comment so repeated /profile_recursion runs update in place. + const existing = comments.find(c => + c.user.type === 'Bot' && + c.body.includes('Recursion guest profile') + ); + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body, + }); + } diff --git a/Cargo.lock b/Cargo.lock index 6a9cae1ef..7c351b9ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,18 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" version = "1.1.4" @@ -23,21 +11,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - [[package]] name = "anes" version = "0.1.6" @@ -94,151 +67,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "anyhow" -version = "1.0.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" - -[[package]] -name = "ark-bn254" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" -dependencies = [ - "ark-ec", - "ark-ff", - "ark-std", -] - -[[package]] -name = "ark-ec" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" -dependencies = [ - "ahash", - "ark-ff", - "ark-poly", - "ark-serialize", - "ark-std", - "educe", - "fnv", - "hashbrown 0.15.5", - "itertools 0.13.0", - "num-bigint", - "num-integer", - "num-traits", - "zeroize", -] - -[[package]] -name = "ark-ff" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" -dependencies = [ - "ark-ff-asm", - "ark-ff-macros", - "ark-serialize", - "ark-std", - "arrayvec", - "digest", - "educe", - "itertools 0.13.0", - "num-bigint", - "num-traits", - "paste", - "zeroize", -] - -[[package]] -name = "ark-ff-asm" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "ark-ff-macros" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" -dependencies = [ - "num-bigint", - "num-traits", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "ark-poly" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" -dependencies = [ - "ahash", - "ark-ff", - "ark-serialize", - "ark-std", - "educe", - "fnv", - "hashbrown 0.15.5", -] - -[[package]] -name = "ark-serialize" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" -dependencies = [ - "ark-serialize-derive", - "ark-std", - "arrayvec", - "digest", - "num-bigint", -] - -[[package]] -name = "ark-serialize-derive" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "ark-std" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" -dependencies = [ - "num-traits", - "rand 0.8.5", -] - -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] -name = "atomic-polyfill" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" -dependencies = [ - "critical-section", -] - [[package]] name = "atty" version = "0.2.14" @@ -262,18 +90,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64ct" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" - [[package]] name = "bincode" version = "1.3.3" @@ -298,22 +114,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" -[[package]] -name = "bitcoin-io" -version = "0.1.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11301df0b06f22dea7bb1916403fdd88a371031e495c49b8f96931b28189e175" - -[[package]] -name = "bitcoin_hashes" -version = "0.14.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9901a56e133a1fc86eeb1113e2591f45f4682451ca893bff494d2f88918e3f" -dependencies = [ - "bitcoin-io", - "hex-conservative", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -326,18 +126,6 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - [[package]] name = "block-buffer" version = "0.10.4" @@ -347,31 +135,12 @@ dependencies = [ "generic-array", ] -[[package]] -name = "bls12_381" -version = "0.8.0" -source = "git+https://github.com/lambdaclass/bls12_381?branch=expose-affine-constructors#78cad0378b17fc3157b83f514be192bf46edf9a1" -dependencies = [ - "digest", - "ff", - "group", - "pairing", - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "bumpalo" version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" -[[package]] -name = "byte-slice-cast" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" - [[package]] name = "bytecheck" version = "0.8.2" @@ -395,27 +164,6 @@ dependencies = [ "syn", ] -[[package]] -name = "bytemuck" -version = "1.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" -dependencies = [ - "serde", -] - [[package]] name = "cast" version = "0.3.0" @@ -438,18 +186,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" -[[package]] -name = "chrono" -version = "0.4.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" -dependencies = [ - "iana-time-zone", - "num-traits", - "serde", - "windows-link", -] - [[package]] name = "ciborium" version = "0.2.2" @@ -474,7 +210,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", - "half 2.7.1", + "half", ] [[package]] @@ -485,7 +221,7 @@ checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "bitflags 1.3.2", "clap_lex 0.2.4", - "indexmap 1.9.3", + "indexmap", "textwrap", ] @@ -542,25 +278,16 @@ checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" name = "cli" version = "0.1.0" dependencies = [ - "bincode", "clap 4.5.53", "env_logger", "executor", "lambda-vm-prover", + "rkyv", "stark", "tikv-jemalloc-ctl", "tikv-jemallocator", ] -[[package]] -name = "cobs" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" -dependencies = [ - "thiserror 2.0.17", -] - [[package]] name = "colorchoice" version = "1.0.4" @@ -573,35 +300,6 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -[[package]] -name = "const_format" -version = "0.2.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" -dependencies = [ - "const_format_proc_macros", -] - -[[package]] -name = "const_format_proc_macros" -version = "0.2.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -617,15 +315,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - [[package]] name = "criterion" version = "0.4.0" @@ -686,34 +375,6 @@ dependencies = [ "itertools 0.10.5", ] -[[package]] -name = "critical-section" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" - -[[package]] -name = "crossbeam" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-epoch", - "crossbeam-queue", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -733,15 +394,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "crossbeam-queue" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -766,6 +418,7 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "rayon", + "rkyv", "serde", "sha2", "sha3", @@ -804,140 +457,39 @@ dependencies = [ ] [[package]] -name = "darling" -version = "0.21.3" +name = "der" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "darling_core", - "darling_macro", + "const-oid", + "zeroize", ] [[package]] -name = "darling_core" -version = "0.21.3" +name = "digest" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn", + "block-buffer", + "crypto-common", ] [[package]] -name = "darling_macro" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +name = "ecsm" +version = "0.1.0" dependencies = [ - "darling_core", - "quote", - "syn", + "k256", + "num-bigint", + "num-traits", ] [[package]] -name = "der" -version = "0.7.10" +name = "either" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid", - "zeroize", -] - -[[package]] -name = "deranged" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" -dependencies = [ - "powerfmt", - "serde_core", -] - -[[package]] -name = "derive_more" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "syn", - "unicode-xid", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", - "subtle", -] - -[[package]] -name = "dyn-clone" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" - -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "digest", - "elliptic-curve", - "rfc6979", - "signature", - "spki", -] - -[[package]] -name = "ecsm" -version = "0.1.0" -dependencies = [ - "k256", - "num-bigint", - "num-traits", -] - -[[package]] -name = "educe" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" -dependencies = [ - "enum-ordinalize", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "elliptic-curve" @@ -947,49 +499,15 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", - "digest", "ff", "generic-array", "group", - "pkcs8", "rand_core 0.6.4", "sec1", "subtle", "zeroize", ] -[[package]] -name = "embedded-io" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" - -[[package]] -name = "embedded-io" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" - -[[package]] -name = "enum-ordinalize" -version = "4.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" -dependencies = [ - "enum-ordinalize-derive", -] - -[[package]] -name = "enum-ordinalize-derive" -version = "4.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "env_filter" version = "0.1.4" @@ -1013,12 +531,6 @@ dependencies = [ "log", ] -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - [[package]] name = "errno" version = "0.3.14" @@ -1029,201 +541,15 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "ethbloom" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c321610643004cf908ec0f5f2aa0d8f1f8e14b540562a2887a1111ff1ecbf7b" -dependencies = [ - "crunchy", - "fixed-hash", - "impl-rlp", - "impl-serde", - "tiny-keccak", -] - -[[package]] -name = "ethereum-types" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ab15ed80916029f878e0267c3a9f92b67df55e79af370bf66199059ae2b4ee3" -dependencies = [ - "ethbloom", - "fixed-hash", - "impl-rlp", - "impl-serde", - "primitive-types", - "uint", -] - -[[package]] -name = "ethrex-common" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" -dependencies = [ - "bytes", - "crc32fast", - "ethereum-types", - "ethrex-crypto", - "ethrex-rlp", - "ethrex-trie", - "hex", - "hex-literal", - "hex-simd", - "indexmap 2.12.1", - "lazy_static", - "libc", - "lru", - "once_cell", - "rayon", - "rkyv", - "rustc-hash", - "secp256k1", - "serde", - "serde_json", - "sha2", - "thiserror 2.0.17", - "tracing", -] - -[[package]] -name = "ethrex-crypto" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" -dependencies = [ - "ark-bn254", - "ark-ec", - "ark-ff", - "bls12_381", - "ethereum-types", - "ff", - "hex-literal", - "k256", - "malachite", - "num-bigint", - "p256", - "ripemd", - "secp256k1", - "sha2", - "thiserror 2.0.17", - "tiny-keccak", -] - -[[package]] -name = "ethrex-guest-program" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" -dependencies = [ - "bytes", - "ethereum-types", - "ethrex-common", - "ethrex-crypto", - "ethrex-l2-common", - "ethrex-rlp", - "ethrex-vm", - "hex", - "rkyv", - "serde", - "serde_with", - "thiserror 2.0.17", -] - -[[package]] -name = "ethrex-l2-common" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" -dependencies = [ - "bytes", - "ethereum-types", - "ethrex-common", - "ethrex-crypto", - "k256", - "lambdaworks-crypto", - "rkyv", - "secp256k1", - "serde", - "serde_with", - "thiserror 2.0.17", - "tracing", -] - -[[package]] -name = "ethrex-levm" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" -dependencies = [ - "bytes", - "derive_more", - "ethrex-common", - "ethrex-crypto", - "ethrex-rlp", - "malachite", - "rayon", - "rustc-hash", - "serde", - "strum", - "thiserror 2.0.17", -] - -[[package]] -name = "ethrex-rlp" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" -dependencies = [ - "bytes", - "ethereum-types", - "thiserror 2.0.17", -] - -[[package]] -name = "ethrex-trie" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" -dependencies = [ - "anyhow", - "bytes", - "crossbeam", - "ethereum-types", - "ethrex-crypto", - "ethrex-rlp", - "lazy_static", - "rayon", - "rkyv", - "rustc-hash", - "serde", - "thiserror 2.0.17", -] - -[[package]] -name = "ethrex-vm" -version = "13.0.0" -source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" -dependencies = [ - "bytes", - "derive_more", - "dyn-clone", - "ethrex-common", - "ethrex-crypto", - "ethrex-levm", - "ethrex-rlp", - "rayon", - "rustc-hash", - "serde", - "thiserror 2.0.17", - "tracing", -] - [[package]] name = "executor" version = "0.1.0" dependencies = [ "ecsm", - "ethrex-guest-program", - "rkyv", "rustc-demangle", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror", "tiny-keccak", ] @@ -1239,7 +565,6 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ - "bitvec", "rand_core 0.6.4", "subtle", ] @@ -1248,292 +573,104 @@ dependencies = [ name = "find-msvc-tools" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" - -[[package]] -name = "fixed-hash" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" -dependencies = [ - "byteorder", - "rand 0.8.5", - "rustc-hex", - "static_assertions", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[package]] -name = "foldhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" - -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", - "zeroize", -] - -[[package]] -name = "getrandom" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasip2", -] - -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "half" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" - -[[package]] -name = "half" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" -dependencies = [ - "cfg-if", - "crunchy", - "zerocopy", -] - -[[package]] -name = "hash32" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" -dependencies = [ - "byteorder", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "allocator-api2", - "foldhash 0.1.5", -] - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash 0.2.0", -] - -[[package]] -name = "hashbrown" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" - -[[package]] -name = "heapless" -version = "0.7.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" -dependencies = [ - "atomic-polyfill", - "hash32", - "rustc_version", - "serde", - "spin", - "stable_deref_trait", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] -name = "hex" -version = "0.4.3" +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "hex-conservative" -version = "0.2.2" +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ - "arrayvec", + "typenum", + "version_check", + "zeroize", ] [[package]] -name = "hex-literal" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" - -[[package]] -name = "hex-simd" -version = "0.8.0" +name = "getrandom" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f7685beb53fc20efc2605f32f5d51e9ba18b8ef237961d1760169d2290d3bee" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ - "outref", - "vsimd", + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", ] [[package]] -name = "hmac" -version = "0.12.1" +name = "getrandom" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ - "digest", + "cfg-if", + "libc", + "r-efi", + "wasip2", ] [[package]] -name = "iana-time-zone" -version = "0.1.64" +name = "group" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", + "ff", + "rand_core 0.6.4", + "subtle", ] [[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" +name = "half" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ - "cc", + "cfg-if", + "crunchy", + "zerocopy", ] [[package]] -name = "ident_case" -version = "1.0.1" +name = "hashbrown" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] -name = "impl-codec" -version = "0.7.1" +name = "hashbrown" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d40b9d5e17727407e55028eafc22b2dc68781786e6d7eb8a21103f5058e3a14" -dependencies = [ - "parity-scale-codec", -] +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] -name = "impl-rlp" -version = "0.4.0" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54ed8ad1f3877f7e775b8cbf30ed1bd3209a95401817f19a0eb4402d13f8cf90" -dependencies = [ - "rlp", -] +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "impl-serde" -version = "0.5.0" +name = "hermit-abi" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a143eada6a1ec4aefa5049037a26a6d597bfd64f8c026d07b77133e02b7dd0b" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ - "serde", + "libc", ] [[package]] -name = "impl-trait-for-tuples" -version = "0.2.3" +name = "hermit-abi" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "indexmap" @@ -1543,19 +680,6 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", - "serde", -] - -[[package]] -name = "indexmap" -version = "2.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" -dependencies = [ - "equivalent", - "hashbrown 0.16.1", - "serde", - "serde_core", ] [[package]] @@ -1593,24 +717,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.16" @@ -1658,11 +764,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ "cfg-if", - "ecdsa", "elliptic-curve", - "once_cell", - "sha2", - "signature", ] [[package]] @@ -1678,7 +780,6 @@ dependencies = [ name = "lambda-vm-prover" version = "0.1.0" dependencies = [ - "bincode", "criterion 0.5.1", "crypto", "ecsm", @@ -1686,9 +787,8 @@ dependencies = [ "executor", "log", "math", - "postcard", "rayon", - "serde", + "rkyv", "sha3", "stark", "sysinfo", @@ -1697,34 +797,6 @@ dependencies = [ "tiny-keccak", ] -[[package]] -name = "lambdaworks-crypto" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58b1a1c1102a5a7fbbda117b79fb3a01e033459c738a3c1642269603484fd1c1" -dependencies = [ - "lambdaworks-math", - "rand 0.8.5", - "rand_chacha 0.3.1", - "serde", - "sha2", - "sha3", -] - -[[package]] -name = "lambdaworks-math" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "018a95aa873eb49896a858dee0d925c33f3978d073c64b08dd4f2c9b35a017c6" -dependencies = [ - "getrandom 0.2.16", - "num-bigint", - "num-traits", - "rand 0.8.5", - "serde", - "serde_json", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -1747,88 +819,18 @@ dependencies = [ "windows-link", ] -[[package]] -name = "libm" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" - [[package]] name = "linux-raw-sys" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - [[package]] name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" -[[package]] -name = "lru" -version = "0.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" -dependencies = [ - "hashbrown 0.16.1", -] - -[[package]] -name = "malachite" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec410515e231332b14cd986a475d1c3323bcfa4c7efc038bfa1d5b410b1c57e4" -dependencies = [ - "malachite-base", - "malachite-nz", - "malachite-q", -] - -[[package]] -name = "malachite-base" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c738d3789301e957a8f7519318fcbb1b92bb95863b28f6938ae5a05be6259f34" -dependencies = [ - "hashbrown 0.15.5", - "itertools 0.14.0", - "libm", - "ryu", -] - -[[package]] -name = "malachite-nz" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1707c9a1fa36ce21749b35972bfad17bbf34cf5a7c96897c0491da321e387d3b" -dependencies = [ - "itertools 0.14.0", - "libm", - "malachite-base", - "wide", -] - -[[package]] -name = "malachite-q" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d764801aa4e96bbb69b389dcd03b50075345131cd63ca2e380bca71cc37a3675" -dependencies = [ - "itertools 0.14.0", - "malachite-base", - "malachite-nz", -] - [[package]] name = "matchers" version = "0.2.0" @@ -1850,6 +852,7 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "rayon", + "rkyv", "serde", "serde_json", ] @@ -1931,12 +934,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - [[package]] name = "num-integer" version = "0.1.46" @@ -1979,61 +976,6 @@ version = "6.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" -[[package]] -name = "outref" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" - -[[package]] -name = "p256" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", -] - -[[package]] -name = "pairing" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" -dependencies = [ - "group", -] - -[[package]] -name = "parity-scale-codec" -version = "3.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" -dependencies = [ - "arrayvec", - "bitvec", - "byte-slice-cast", - "const_format", - "impl-trait-for-tuples", - "parity-scale-codec-derive", - "rustversion", - "serde", -] - -[[package]] -name = "parity-scale-codec-derive" -version = "3.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "paste" version = "1.0.15" @@ -2046,16 +988,6 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - [[package]] name = "plotters" version = "0.3.7" @@ -2099,25 +1031,6 @@ dependencies = [ "portable-atomic", ] -[[package]] -name = "postcard" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" -dependencies = [ - "cobs", - "embedded-io 0.4.0", - "embedded-io 0.6.1", - "heapless", - "serde", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - [[package]] name = "ppv-lite86" version = "0.2.21" @@ -2127,37 +1040,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "primeorder" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" -dependencies = [ - "elliptic-curve", -] - -[[package]] -name = "primitive-types" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5" -dependencies = [ - "fixed-hash", - "impl-codec", - "impl-rlp", - "impl-serde", - "uint", -] - -[[package]] -name = "proc-macro-crate" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" -dependencies = [ - "toml_edit", -] - [[package]] name = "proc-macro2" version = "1.0.103" @@ -2227,12 +1109,6 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - [[package]] name = "rancor" version = "0.1.1" @@ -2326,28 +1202,8 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "ref-cast" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "crossbeam-deque", + "crossbeam-utils", ] [[package]] @@ -2388,25 +1244,6 @@ dependencies = [ "bytecheck", ] -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac", - "subtle", -] - -[[package]] -name = "ripemd" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" -dependencies = [ - "digest", -] - [[package]] name = "rkyv" version = "0.8.16" @@ -2414,16 +1251,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73389e0c99e664f919275ab5b5b0471391fe9a8de61e1dff9b1eaf56a90f16e3" dependencies = [ "bytecheck", - "bytes", "hashbrown 0.17.1", - "indexmap 2.12.1", "munge", "ptr_meta", "rancor", "rend", "rkyv_derive", "tinyvec", - "uuid", ] [[package]] @@ -2437,43 +1271,12 @@ dependencies = [ "syn", ] -[[package]] -name = "rlp" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa24e92bb2a83198bb76d661a71df9f7076b8c420b8696e4d3d97d50d94479e3" -dependencies = [ - "bytes", - "rustc-hex", -] - [[package]] name = "rustc-demangle" version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" - -[[package]] -name = "rustc-hex" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - [[package]] name = "rustix" version = "1.1.3" @@ -2511,15 +1314,6 @@ version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" -[[package]] -name = "safe_arch" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" -dependencies = [ - "bytemuck", -] - [[package]] name = "same-file" version = "1.0.6" @@ -2529,36 +1323,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schemars" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "sec1" version = "0.7.3" @@ -2568,37 +1332,10 @@ dependencies = [ "base16ct", "der", "generic-array", - "pkcs8", "subtle", "zeroize", ] -[[package]] -name = "secp256k1" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" -dependencies = [ - "bitcoin_hashes", - "rand 0.8.5", - "secp256k1-sys", -] - -[[package]] -name = "secp256k1-sys" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" -dependencies = [ - "cc", -] - -[[package]] -name = "semver" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" - [[package]] name = "serde" version = "1.0.228" @@ -2609,27 +1346,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-wasm-bindgen" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" -dependencies = [ - "js-sys", - "serde", - "wasm-bindgen", -] - -[[package]] -name = "serde_cbor" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" -dependencies = [ - "half 1.8.3", - "serde", -] - [[package]] name = "serde_core" version = "1.0.228" @@ -2663,37 +1379,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "serde_with" -version = "3.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" -dependencies = [ - "base64", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.12.1", - "schemars 0.9.0", - "schemars 1.2.0", - "serde_core", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "sha2" version = "0.10.9" @@ -2730,47 +1415,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core 0.6.4", -] - [[package]] name = "simdutf8" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - [[package]] name = "stark" version = "0.1.0" @@ -2788,50 +1438,21 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "rayon", - "serde", - "serde-wasm-bindgen", - "serde_cbor", + "rkyv", "sha3", "tempfile", "test-log", - "thiserror 1.0.69", + "thiserror", "wasm-bindgen", "web-sys", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "strum" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "subtle" version = "2.6.1" @@ -2862,12 +1483,6 @@ dependencies = [ "windows", ] -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - [[package]] name = "tempfile" version = "3.23.0" @@ -2915,16 +1530,7 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" -dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl", ] [[package]] @@ -2938,17 +1544,6 @@ dependencies = [ "syn", ] -[[package]] -name = "thiserror-impl" -version = "2.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "thread_local" version = "1.1.9" @@ -2989,37 +1584,6 @@ dependencies = [ "tikv-jemalloc-sys", ] -[[package]] -name = "time" -version = "0.3.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" - -[[package]] -name = "time-macros" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" -dependencies = [ - "num-conv", - "time-core", -] - [[package]] name = "tiny-keccak" version = "2.0.2" @@ -3054,59 +1618,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.23.10+spec-1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" -dependencies = [ - "indexmap 2.12.1", - "toml_datetime", - "toml_parser", - "winnow", -] - -[[package]] -name = "toml_parser" -version = "1.0.6+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" -dependencies = [ - "winnow", -] - [[package]] name = "tracing" version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ - "log", "pin-project-lite", - "tracing-attributes", "tracing-core", ] -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "tracing-core" version = "0.1.36" @@ -3151,18 +1672,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" -[[package]] -name = "uint" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - [[package]] name = "unarray" version = "0.1.4" @@ -3175,34 +1684,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "uuid" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "valuable" version = "0.1.1" @@ -3215,12 +1702,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "vsimd" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" - [[package]] name = "wait-timeout" version = "0.2.1" @@ -3310,16 +1791,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "wide" -version = "0.7.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" -dependencies = [ - "bytemuck", - "safe_arch", -] - [[package]] name = "winapi" version = "0.3.9" @@ -3483,30 +1954,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "winnow" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" -dependencies = [ - "memchr", -] - [[package]] name = "wit-bindgen" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - [[package]] name = "zerocopy" version = "0.8.31" @@ -3532,17 +1985,3 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/Makefile b/Makefile index 454eff098..5fbe41f2c 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ .PHONY: deps deps-linux deps-macos compile-programs-asm compile-programs-rust compile-bench \ compile-programs compile-recursion-elfs clean-asm clean-rust clean-bench clean-shared \ clean-recursion-elfs clean test test-asm \ -test-rust test-executor test-flamegraph flamegraph-prover \ -test-fast test-prover test-prover-all test-disk-spill test-math-cuda test-cuda-integration \ +test-rust test-executor test-flamegraph flamegraph-prover test-profile-recursion test-profile-recursion-single test-profile-recursion-multi \ +test-fast test-prover test-prover-all test-disk-spill test-math-cuda test-cuda-integration test-ethrex \ bench-math-cuda bench-prover bench-prover-cuda build check clippy fmt lint regen-ethrex-fixtures \ update-ethrex-fixture-checksums check-ethrex-fixture-checksums @@ -232,6 +232,14 @@ test-rust: compile-programs-rust test-flamegraph: cargo test -p executor --test flamegraph +test-profile-recursion: test-profile-recursion-single test-profile-recursion-multi + +test-profile-recursion-single: compile-recursion-elfs + cargo test --package lambda-vm-prover --lib test_recursion_profile_1query -- --ignored --nocapture + +test-profile-recursion-multi: compile-recursion-elfs + cargo test --package lambda-vm-prover --lib test_recursion_profile_multiquery -- --ignored --nocapture + # Regenerate the committed ethrex block fixtures (see tooling/ethrex-fixtures). # Run after bumping the ethrex rev; README checksums are refreshed automatically. regen-ethrex-fixtures: @@ -247,8 +255,14 @@ update-ethrex-fixture-checksums: check-ethrex-fixture-checksums: python3 tooling/ethrex-fixtures/update_readme_checksums.py --check +# Detached workspace: ethrex pins rkyv `unaligned`, which must not feature-unify +# with the main workspace's aligned proof format (see tooling/ethrex-tests). +test-ethrex: compile-programs-rust + cd tooling/ethrex-tests && cargo test + test: compile-programs cargo test + $(MAKE) test-ethrex # === Quick test shortcuts === diff --git a/bench_vs/lambda/fibonacci/src/main.rs b/bench_vs/lambda/fibonacci/src/main.rs index 9ef9ef69c..d3c595e37 100644 --- a/bench_vs/lambda/fibonacci/src/main.rs +++ b/bench_vs/lambda/fibonacci/src/main.rs @@ -15,7 +15,7 @@ fn panic(_info: &PanicInfo) -> ! { fn read_n() -> u64 { // Layout matches `syscalls::get_private_input`: 4-byte LE length prefix at - // PRIVATE_INPUT_START, payload at +4. We only need the first 8 bytes (u64). + // PRIVATE_INPUT_START, payload at +16. We only need the first 8 bytes (u64). let mut n_bytes = [0u8; 8]; debug_assert!( @@ -23,7 +23,7 @@ fn read_n() -> u64 { "private input too short to contain a u64" ); - let input_data = (PRIVATE_INPUT_START + 4) as *const u8; + let input_data = (PRIVATE_INPUT_START + 16) as *const u8; n_bytes.copy_from_slice(unsafe { core::slice::from_raw_parts(input_data, 8) }); u64::from_le_bytes(n_bytes) diff --git a/bench_vs/lambda/recursion/Cargo.lock b/bench_vs/lambda/recursion/Cargo.lock index 66048ba81..0e0cd0796 100644 --- a/bench_vs/lambda/recursion/Cargo.lock +++ b/bench_vs/lambda/recursion/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "atomic-polyfill" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" -dependencies = [ - "critical-section", -] - [[package]] name = "autocfg" version = "1.5.1" @@ -45,25 +36,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] -name = "byteorder" -version = "1.5.0" +name = "bytecheck" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "0caa33a2c0edca0419d15ac723dff03f1956f7978329b1e3b5fdaaaed9d3ca8b" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "rancor", + "simdutf8", +] [[package]] -name = "cfg-if" -version = "1.0.4" +name = "bytecheck_derive" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +checksum = "89385e82b5d1821d2219e0b095efa2cc1f246cbf99080f3be46a1a85c0d392d9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] [[package]] -name = "cobs" -version = "0.3.0" +name = "cfg-if" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" -dependencies = [ - "thiserror 2.0.18", -] +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "const-default" @@ -131,6 +130,7 @@ dependencies = [ "math", "rand 0.8.6", "rand_chacha 0.3.1", + "rkyv", "serde", "sha3", ] @@ -227,25 +227,13 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" -[[package]] -name = "embedded-io" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" - -[[package]] -name = "embedded-io" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" - [[package]] name = "executor" version = "0.1.0" dependencies = [ "ecsm", "rustc-demangle", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -330,33 +318,10 @@ dependencies = [ ] [[package]] -name = "half" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" - -[[package]] -name = "hash32" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" -dependencies = [ - "byteorder", -] - -[[package]] -name = "heapless" -version = "0.7.17" +name = "hashbrown" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" -dependencies = [ - "atomic-polyfill", - "hash32", - "rustc_version", - "serde", - "spin", - "stable_deref_trait", -] +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "itertools" @@ -412,7 +377,7 @@ dependencies = [ "executor", "log", "math", - "serde", + "rkyv", "sha3", "stark", "sysinfo", @@ -428,7 +393,7 @@ dependencies = [ "lazy_static", "rand 0.9.4", "riscv", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -449,15 +414,6 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b23ac50abb8261cb38c6e2a7192d3302e0836dac1628f6a93b82b4fad185897" -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - [[package]] name = "log" version = "0.4.33" @@ -473,6 +429,7 @@ dependencies = [ "num-traits", "rand 0.8.6", "rayon", + "rkyv", "serde", "serde_json", ] @@ -483,6 +440,26 @@ version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" +[[package]] +name = "munge" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e17401f259eba956ca16491461b6e8f72913a0a114e39736ce404410f915a0c" +dependencies = [ + "munge_macro", +] + +[[package]] +name = "munge_macro" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4568f25ccbd45ab5d5603dc34318c1ec56b117531781260002151b8530a9f931" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "ntapi" version = "0.4.3" @@ -538,19 +515,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "postcard" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" -dependencies = [ - "cobs", - "embedded-io 0.4.0", - "embedded-io 0.6.1", - "heapless", - "serde", -] - [[package]] name = "ppv-lite86" version = "0.2.21" @@ -569,6 +533,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "ptr_meta" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9a0cf95a1196af61d4f1cbdab967179516d9a4a4312af1f31948f8f6224a79" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7347867d0a7e1208d93b46767be83e2b8f978c3dad35f775ac8d8847551d6fe1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "quote" version = "1.0.46" @@ -584,6 +568,15 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rancor" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daff8b7b3ccf5f7ba270b3e7a0a4d4c701c5797e38dec27c7e2c3dbb830fed1c" +dependencies = [ + "ptr_meta", +] + [[package]] name = "rand" version = "0.8.6" @@ -664,7 +657,15 @@ version = "0.1.0" dependencies = [ "lambda-vm-prover", "lambda-vm-syscalls", - "postcard", +] + +[[package]] +name = "rend" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "663ba70707f96e871406fe10d68128412e619b06d1d47cb91c3a4c6501176240" +dependencies = [ + "bytecheck", ] [[package]] @@ -697,6 +698,33 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8188909339ccc0c68cfb5a04648313f09621e8b87dc03095454f1a11f6c5d436" +[[package]] +name = "rkyv" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "815cc8a37159a463064825246cadb07961e25cd9885908606f6d08a98d8f8874" +dependencies = [ + "bytecheck", + "hashbrown", + "munge", + "ptr_meta", + "rancor", + "rend", + "rkyv_derive", + "tinyvec", +] + +[[package]] +name = "rkyv_derive" +version = "0.8.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ed1a78a1b19d184b0daa629dd9a024573173ec7d485b287cb369fb3607cc1c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "rlsf" version = "0.2.2" @@ -716,27 +744,12 @@ version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "sec1" version = "0.7.3" @@ -750,12 +763,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "semver" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" - [[package]] name = "serde" version = "1.0.228" @@ -766,16 +773,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde_cbor" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" -dependencies = [ - "half", - "serde", -] - [[package]] name = "serde_core" version = "1.0.228" @@ -820,25 +817,16 @@ dependencies = [ ] [[package]] -name = "slab" -version = "0.4.12" +name = "simdutf8" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" +name = "slab" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "stark" @@ -848,10 +836,9 @@ dependencies = [ "itertools", "log", "math", - "serde", - "serde_cbor", + "rkyv", "sha3", - "thiserror 1.0.69", + "thiserror", ] [[package]] @@ -914,16 +901,7 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" -dependencies = [ - "thiserror-impl 2.0.18", + "thiserror-impl", ] [[package]] @@ -938,16 +916,20 @@ dependencies = [ ] [[package]] -name = "thiserror-impl" -version = "2.0.18" +name = "tinyvec" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", + "tinyvec_macros", ] +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "typenum" version = "1.20.1" diff --git a/bench_vs/lambda/recursion/Cargo.toml b/bench_vs/lambda/recursion/Cargo.toml index bdfeb38dc..6a926546f 100644 --- a/bench_vs/lambda/recursion/Cargo.toml +++ b/bench_vs/lambda/recursion/Cargo.toml @@ -6,6 +6,7 @@ version = "0.1.0" edition = "2024" [dependencies] -lambda-vm-prover = { path = "../../../prover", default-features = false } +lambda-vm-prover = { path = "../../../prover", default-features = false, features = [ + "profile-markers", +] } lambda-vm-syscalls = { path = "../../../syscalls" } -postcard = { version = "1.0", features = ["alloc"] } diff --git a/bench_vs/lambda/recursion/src/main.rs b/bench_vs/lambda/recursion/src/main.rs index c256a0732..f9d3215db 100644 --- a/bench_vs/lambda/recursion/src/main.rs +++ b/bench_vs/lambda/recursion/src/main.rs @@ -1,9 +1,16 @@ //! Naive recursion guest: verifies an inner lambda-vm proof inside the VM. //! -//! Private input layout (postcard-encoded): -//! `(VmProof, Vec, ProofOptions)` -//! where the `Vec` holds the inner program's ELF bytes and `ProofOptions` -//! specifies the parameters the inner prover used. Commits `[1]` on success. +//! Private input layout: a 16-byte `"LVMR" + version + reserved` prefix +//! followed by an rkyv archive of `lambda_vm_prover::RecursionInput` +//! `{ vm_proof, inner_elf, options, vkey }`. The prefix tags the format so the +//! guest rejects a wrong-format blob before the unsafe access; sized to a +//! multiple of 16, it keeps the archive 16-aligned at the executor's aligned +//! payload base (`PRIVATE_INPUT_START + 16`). The proof is verified **in +//! place** via `verify_recursion_blob` — no deserialization pass, no owned +//! `VmProof`. Commits `vk_digest ‖ inner public output` on success: every +//! input here is prover-supplied, so soundness comes from the outer verifier +//! checking the committed digest against one derived from the trusted inner +//! ELF. //! //! Not `no_std` (std/alloc are available — `build-std` provides them, and the //! prover links as a normal std crate; its prove-side code is dead-code @@ -14,8 +21,6 @@ #![no_main] -use lambda_vm_prover::{ProofOptions, VmProof}; - #[unsafe(export_name = "main")] pub fn main() -> ! { lambda_vm_syscalls::allocator::init_allocator(); @@ -28,14 +33,20 @@ pub fn main() -> ! { lambda_vm_syscalls::syscalls::sys_panic(PANIC_MSG.as_ptr(), PANIC_MSG.len()) })); - let blob = lambda_vm_syscalls::syscalls::get_private_input(); - let (vm_proof, inner_elf, options): (VmProof, Vec, ProofOptions) = - postcard::from_bytes(&blob).expect("failed to deserialize recursion input"); + // Zero-copy: borrow the blob straight from the mapped private-input region. + // The payload base and prefix are both 16-aligned, so the archive sits at a + // 16-aligned guest address and the verifier's in-place loads don't trap. + let blob = lambda_vm_syscalls::syscalls::get_private_input_slice(); + lambda_vm_prover::profile_markers::step_marker::< + { lambda_vm_prover::profile_markers::STEP_DECODE_DONE }, + >(); - let ok = lambda_vm_prover::verify_with_options(&vm_proof, &inner_elf, &options, None, None) - .expect("verify errored"); - assert!(ok, "inner proof failed verification"); + let verification = lambda_vm_prover::verify_recursion_blob(blob).expect("verify errored"); + assert!(verification.ok, "inner proof failed verification"); - lambda_vm_syscalls::syscalls::commit(&[1u8]); + let mut output = Vec::with_capacity(32 + verification.public_output.len()); + output.extend_from_slice(&verification.vk_digest); + output.extend_from_slice(verification.public_output); + lambda_vm_syscalls::syscalls::commit(&output); lambda_vm_syscalls::syscalls::sys_halt(); } diff --git a/bin/cli/Cargo.toml b/bin/cli/Cargo.toml index 87bb1c8fc..c0179c74d 100644 --- a/bin/cli/Cargo.toml +++ b/bin/cli/Cargo.toml @@ -9,7 +9,7 @@ executor = { path = "../../executor" } prover = { path = "../../prover", package = "lambda-vm-prover" } stark = { path = "../../crypto/stark" } clap = { version = "4.3.10", features = ["derive"] } -bincode = "1" +rkyv = { version = "0.8.10", default-features = false, features = ["alloc", "bytecheck", "aligned"] } tikv-jemallocator = "0.6" tikv-jemalloc-ctl = { version = "0.6", features = ["stats"], optional = true } env_logger = "0.11" diff --git a/bin/cli/src/main.rs b/bin/cli/src/main.rs index 2b053755c..cf43863b8 100644 --- a/bin/cli/src/main.rs +++ b/bin/cli/src/main.rs @@ -475,7 +475,7 @@ fn cmd_prove( }; let mut writer = BufWriter::new(file); - let bytes = match bincode::serialize(&proof) { + let bytes = match rkyv::to_bytes::(&proof) { Ok(b) => b, Err(e) => { eprintln!("Failed to serialize proof: {}", e); @@ -526,7 +526,7 @@ fn cmd_verify(proof_path: PathBuf, elf_path: PathBuf, blowup: u8, time: bool) -> } }; - let proof: VmProof = match bincode::deserialize(&proof_bytes) { + let proof: VmProof = match rkyv::from_bytes::(&proof_bytes) { Ok(p) => p, Err(e) => { eprintln!("Failed to deserialize proof: {}", e); @@ -647,7 +647,7 @@ fn cmd_prove_continuation( } }; let mut writer = BufWriter::new(file); - let bytes = match bincode::serialize(&bundle) { + let bytes = match rkyv::to_bytes::(&bundle) { Ok(b) => b, Err(e) => { eprintln!("Failed to serialize proof: {}", e); @@ -693,7 +693,11 @@ fn cmd_verify_continuation( return ExitCode::FAILURE; } }; - let bundle: prover::continuation::ContinuationProof = match bincode::deserialize(&proof_bytes) { + let bundle: prover::continuation::ContinuationProof = match rkyv::from_bytes::< + prover::continuation::ContinuationProof, + rkyv::rancor::Error, + >(&proof_bytes) + { Ok(p) => p, Err(e) => { eprintln!("Failed to deserialize proof: {}", e); diff --git a/crypto/crypto/Cargo.toml b/crypto/crypto/Cargo.toml index 6e3731beb..c8f7bc6a5 100644 --- a/crypto/crypto/Cargo.toml +++ b/crypto/crypto/Cargo.toml @@ -22,6 +22,11 @@ rand_chacha = { version = "0.3.1", default-features = false } memmap2 = { version = "0.9", optional = true } tempfile = { version = "3", optional = true } libc = { version = "0.2", optional = true } +rkyv = { version = "0.8.10", default-features = false, features = [ + "alloc", + "bytecheck", + "aligned", +], optional = true } [dev-dependencies] math = { path = "../math", features = ["test-utils"] } @@ -37,4 +42,5 @@ std = ["math/std", "sha3/std", "serde?/std"] serde = ["dep:serde"] parallel = ["dep:rayon"] disk-spill = ["std", "dep:memmap2", "dep:tempfile", "dep:libc"] -alloc = [] \ No newline at end of file +alloc = [] +rkyv = ["dep:rkyv", "math/rkyv"] \ No newline at end of file diff --git a/crypto/crypto/src/merkle_tree/proof.rs b/crypto/crypto/src/merkle_tree/proof.rs index 20d5452a2..2bbcfb3c5 100644 --- a/crypto/crypto/src/merkle_tree/proof.rs +++ b/crypto/crypto/src/merkle_tree/proof.rs @@ -15,29 +15,49 @@ use super::{ /// when verifying. #[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "rkyv", + derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize) +)] pub struct Proof { pub merkle_path: Vec, } +/// Verifies a Merkle inclusion proof given the authentication path as a borrowed +/// slice. Shared by [`Proof::verify`] (owned) and the zero-copy verifier (which +/// reads the path straight from an rkyv-archived proof buffer) so both compute +/// the identical root. +pub fn verify_merkle_path( + merkle_path: &[B::Node], + root_hash: &B::Node, + mut index: usize, + value: &B::Data, +) -> bool +where + B: IsMerkleTreeBackend, +{ + let mut hashed_value = B::hash_data(value); + + for sibling_node in merkle_path.iter() { + if index.is_multiple_of(2) { + hashed_value = B::hash_new_parent(&hashed_value, sibling_node); + } else { + hashed_value = B::hash_new_parent(sibling_node, &hashed_value); + } + + index >>= 1; + } + + root_hash == &hashed_value +} + impl Proof { /// Verifies a Merkle inclusion proof for the value contained at leaf index. - pub fn verify(&self, root_hash: &B::Node, mut index: usize, value: &B::Data) -> bool + pub fn verify(&self, root_hash: &B::Node, index: usize, value: &B::Data) -> bool where B: IsMerkleTreeBackend, { - let mut hashed_value = B::hash_data(value); - - for sibling_node in self.merkle_path.iter() { - if index.is_multiple_of(2) { - hashed_value = B::hash_new_parent(&hashed_value, sibling_node); - } else { - hashed_value = B::hash_new_parent(sibling_node, &hashed_value); - } - - index >>= 1; - } - - root_hash == &hashed_value + verify_merkle_path::(&self.merkle_path, root_hash, index, value) } } diff --git a/crypto/math/Cargo.toml b/crypto/math/Cargo.toml index 85979a7c4..df43ea975 100644 --- a/crypto/math/Cargo.toml +++ b/crypto/math/Cargo.toml @@ -23,6 +23,14 @@ rayon = { version = "1.7", optional = true } num-bigint = { version = "0.4.6", default-features = false } num-traits = { version = "0.2.19", default-features = false } +# rkyv zero-copy (de)serialization. Optional; used by the recursion verifier to +# read a proof straight from its byte buffer with no deserialization pass. +rkyv = { version = "0.8.10", default-features = false, features = [ + "alloc", + "bytecheck", + "aligned", +], optional = true } + [dev-dependencies] rand_chacha = "0.3.1" criterion = "0.5.1" @@ -39,6 +47,7 @@ lambdaworks-serde-string = ["dep:serde", "dep:serde_json", "alloc"] proptest = ["dep:proptest"] instruments = [] test-utils = [] +rkyv = ["dep:rkyv"] [target.wasm32-unknown-unknown.dependencies] getrandom = { version = "0.2.15", features = ["js"] } diff --git a/crypto/math/src/field/element.rs b/crypto/math/src/field/element.rs index 0eb0aef96..d5aa07e57 100644 --- a/crypto/math/src/field/element.rs +++ b/crypto/math/src/field/element.rs @@ -850,3 +850,160 @@ impl<'de, F: IsPrimeField> Deserialize<'de> for FieldElement { deserializer.deserialize_struct("FieldElement", FIELDS, FieldElementVisitor(PhantomData)) } } + +// ============================================================================ +// rkyv zero-copy (de)serialization +// ============================================================================ +// +// `FieldElement` is `#[repr(transparent)]` over `F::BaseType`. Its archived +// form is a local `#[repr(transparent)]` newtype wrapping the archived form of +// `F::BaseType` (e.g. archived `u64` for Goldilocks, `[ArchivedFieldElement; 3]` +// for the cubic extension). Keeping it a LOCAL type (rather than reusing +// `::Archived` directly) is what lets us implement +// `Deserialize` without colliding with rkyv's blanket impls — while the +// transparent repr keeps the archived bytes identical to the base type, so the +// recursion verifier still reads field elements straight from the proof buffer. + +/// Archived form of [`FieldElement`]; see the module note above. +#[cfg(feature = "rkyv")] +#[repr(transparent)] +pub struct ArchivedFieldElement +where + F::BaseType: rkyv::Archive, +{ + value: ::Archived, +} + +#[cfg(feature = "rkyv")] +const _: () = { + use rkyv::{Archive, Deserialize, Place, Portable, Serialize}; + + // SAFETY: `ArchivedFieldElement` is `#[repr(transparent)]` over the base + // type's archived form, which is itself `Portable` (required by `Archive`). + // A transparent wrapper over a `Portable` type is position-independent and + // valid for the same byte patterns, so it is `Portable` too. + unsafe impl Portable for ArchivedFieldElement + where + F: IsField, + F::BaseType: Archive, + ::Archived: Portable, + { + } + + impl Archive for FieldElement + where + F: IsField, + F::BaseType: Archive, + { + type Archived = ArchivedFieldElement; + type Resolver = ::Resolver; + + #[inline] + fn resolve(&self, resolver: Self::Resolver, out: Place) { + // `ArchivedFieldElement` is `#[repr(transparent)]` over the base + // type's archived form, so resolving into the inner field resolves + // the whole newtype. + let inner = unsafe { out.cast_unchecked::<::Archived>() }; + self.value.resolve(resolver, inner); + } + } + + impl Serialize for FieldElement + where + F: IsField, + F::BaseType: Serialize, + S: rkyv::rancor::Fallible + ?Sized, + { + #[inline] + fn serialize(&self, serializer: &mut S) -> Result { + self.value.serialize(serializer) + } + } + + impl Deserialize, D> for ArchivedFieldElement + where + F: IsField, + F::BaseType: Archive, + ::Archived: Deserialize, + D: rkyv::rancor::Fallible + ?Sized, + { + #[inline] + fn deserialize(&self, deserializer: &mut D) -> Result, D::Error> { + Ok(FieldElement { + value: self.value.deserialize(deserializer)?, + }) + } + } + + impl ArchivedFieldElement + where + F: IsField, + F::BaseType: Archive, + { + /// Borrow the archived base-type value (for zero-copy reads). + #[inline] + pub fn archived_value(&self) -> &::Archived { + &self.value + } + } + + // SAFETY: `#[repr(transparent)]` over the inner archived value, so checking + // the inner type's bytes checks the whole newtype. + unsafe impl rkyv::bytecheck::CheckBytes for ArchivedFieldElement + where + F: IsField, + F::BaseType: Archive, + ::Archived: rkyv::bytecheck::CheckBytes, + C: rkyv::rancor::Fallible + ?Sized, + { + unsafe fn check_bytes(value: *const Self, context: &mut C) -> Result<(), C::Error> { + unsafe { + <::Archived as rkyv::bytecheck::CheckBytes>::check_bytes( + value as *const ::Archived, + context, + ) + } + } + } +}; + +// ---------------------------------------------------------------------------- +// Zero-copy native views (little-endian only) +// ---------------------------------------------------------------------------- +// +// rkyv archives integers as `rend::*_le` types, which are `#[repr(C, align(N))]` +// and bit-identical to the native little-endian primitive. `FieldElement` is +// `#[repr(transparent)]` over `F::BaseType` and `ArchivedFieldElement` is +// `#[repr(transparent)]` over `::Archived`. So on a +// little-endian target the two types share size, alignment, and bit layout — +// an archived field element *is* a native field element. These views let the +// verifier read field elements straight out of the proof buffer with no copy +// and no allocation. +// +// Restricted to `target_endian = "little"` (the lambda-vm guest target). On a +// big-endian host these would be wrong, so they simply don't exist there. +#[cfg(all(feature = "rkyv", target_endian = "little"))] +impl ArchivedFieldElement +where + F::BaseType: rkyv::Archive, +{ + /// Reinterpret this archived element as a native [`FieldElement`] (no copy). + /// + /// Sound on little-endian: see the module note above. + #[inline] + pub fn as_native(&self) -> &FieldElement { + // SAFETY: identical size/align/bit-layout on little-endian. + unsafe { &*(self as *const Self as *const FieldElement) } + } + + /// Reinterpret a slice of archived elements as a slice of native + /// [`FieldElement`]s (no copy, no allocation). + #[inline] + pub fn slice_as_native(slice: &[Self]) -> &[FieldElement] { + // SAFETY: element-wise identical layout on little-endian, so the slice + // (same length, same element stride) reinterprets directly. + unsafe { + core::slice::from_raw_parts(slice.as_ptr() as *const FieldElement, slice.len()) + } + } +} diff --git a/crypto/math/src/field/extensions_goldilocks.rs b/crypto/math/src/field/extensions_goldilocks.rs index 45fd7274b..feb5c69ed 100644 --- a/crypto/math/src/field/extensions_goldilocks.rs +++ b/crypto/math/src/field/extensions_goldilocks.rs @@ -199,6 +199,7 @@ impl IsField for Degree2GoldilocksExtensionField { } impl IsSubFieldOf for GoldilocksField { + #[inline(always)] fn mul( a: &Self::BaseType, b: &::BaseType, @@ -208,6 +209,7 @@ impl IsSubFieldOf for GoldilocksField { [c0, c1] } + #[inline(always)] fn add( a: &Self::BaseType, b: &::BaseType, @@ -224,6 +226,7 @@ impl IsSubFieldOf for GoldilocksField { Ok(>::mul(a, &b_inv)) } + #[inline(always)] fn sub( a: &Self::BaseType, b: &::BaseType, @@ -410,6 +413,7 @@ impl IsField for Degree3GoldilocksExtensionField { } impl IsSubFieldOf for GoldilocksField { + #[inline(always)] fn mul( a: &Self::BaseType, b: &::BaseType, @@ -420,6 +424,7 @@ impl IsSubFieldOf for GoldilocksField { [c0, c1, c2] } + #[inline(always)] fn add( a: &Self::BaseType, b: &::BaseType, @@ -436,6 +441,7 @@ impl IsSubFieldOf for GoldilocksField { Ok(>::mul(a, &b_inv)) } + #[inline(always)] fn sub( a: &Self::BaseType, b: &::BaseType, diff --git a/crypto/stark/Cargo.toml b/crypto/stark/Cargo.toml index d0f6a51ef..e205810ae 100644 --- a/crypto/stark/Cargo.toml +++ b/crypto/stark/Cargo.toml @@ -9,16 +9,13 @@ crate-type = ["cdylib", "rlib"] [dependencies] -math = { path = "../math", features = [ - "std", - "lambdaworks-serde-binary", -] } -crypto = { path = "../crypto", features = ["std", "serde"] } +math = { path = "../math", features = ["std", "rkyv"] } +crypto = { path = "../crypto", features = ["std", "rkyv"] } thiserror = "1.0.38" log = "0.4.17" sha3 = "0.10.8" -serde = { version = "1.0", features = ["derive"] } itertools = "0.11.0" +rkyv = { version = "0.8.10", default-features = false, features = ["alloc", "bytecheck", "aligned"] } # Parallelization crates rayon = { version = "1.8.0", optional = true } @@ -32,9 +29,7 @@ math-cuda = { path = "../math-cuda", optional = true } # wasm wasm-bindgen = { version = "0.2", optional = true } -serde-wasm-bindgen = { version = "0.5", optional = true } web-sys = { version = "0.3.64", features = ['console'], optional = true } -serde_cbor = { version = "0.11.1" } [dev-dependencies] criterion = { version = "0.4", default-features = false } @@ -48,11 +43,12 @@ rand_chacha = "0.3.1" test-utils = [] test_fiat_shamir = [] instruments = [] # This enables timing prints in prover and verifier +profile-markers = [] # Emits inlining-immune asm markers for guest step profiling debug-checks = [] # Enables validate_trace + bus balance report in prover parallel = ["dep:rayon", "crypto/parallel"] cuda = ["dep:math-cuda"] test-cuda-faults = ["cuda", "math-cuda/test-faults"] -wasm = ["dep:wasm-bindgen", "dep:serde-wasm-bindgen", "dep:web-sys"] +wasm = ["dep:wasm-bindgen", "dep:web-sys"] disk-spill = ["dep:memmap2", "dep:tempfile", "dep:libc", "crypto/disk-spill"] diff --git a/crypto/stark/src/examples/fibonacci_2_cols_shifted.rs b/crypto/stark/src/examples/fibonacci_2_cols_shifted.rs index 76c8ea11f..fae17e5ba 100644 --- a/crypto/stark/src/examples/fibonacci_2_cols_shifted.rs +++ b/crypto/stark/src/examples/fibonacci_2_cols_shifted.rs @@ -137,7 +137,7 @@ where } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct PublicInputs where F: IsFFTField, diff --git a/crypto/stark/src/examples/fibonacci_multi_column.rs b/crypto/stark/src/examples/fibonacci_multi_column.rs index ac6069ece..43efa8566 100644 --- a/crypto/stark/src/examples/fibonacci_multi_column.rs +++ b/crypto/stark/src/examples/fibonacci_multi_column.rs @@ -113,7 +113,7 @@ where /// Public inputs for the multi-column Fibonacci AIR. /// Contains the initial values (first two elements) for each column. -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct FibonacciMultiColumnPublicInputs { /// Initial values for each column: (a0, a1) pairs pub initial_values: Vec<(FieldElement, FieldElement)>, diff --git a/crypto/stark/src/examples/fibonacci_rap.rs b/crypto/stark/src/examples/fibonacci_rap.rs index 10f1827d2..9d359bf26 100644 --- a/crypto/stark/src/examples/fibonacci_rap.rs +++ b/crypto/stark/src/examples/fibonacci_rap.rs @@ -164,7 +164,7 @@ where transition_constraints: Vec>>, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct FibonacciRAPPublicInputs where F: IsFFTField, diff --git a/crypto/stark/src/examples/quadratic_air.rs b/crypto/stark/src/examples/quadratic_air.rs index d49b0050d..6a7fcc3da 100644 --- a/crypto/stark/src/examples/quadratic_air.rs +++ b/crypto/stark/src/examples/quadratic_air.rs @@ -81,7 +81,7 @@ where constraints: Vec>>, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct QuadraticPublicInputs where F: IsFFTField, diff --git a/crypto/stark/src/examples/read_only_memory.rs b/crypto/stark/src/examples/read_only_memory.rs index 8c3e9efac..eef4b47bc 100644 --- a/crypto/stark/src/examples/read_only_memory.rs +++ b/crypto/stark/src/examples/read_only_memory.rs @@ -232,7 +232,7 @@ where transition_constraints: Vec>>, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct ReadOnlyPublicInputs where F: IsFFTField, diff --git a/crypto/stark/src/examples/read_only_memory_logup.rs b/crypto/stark/src/examples/read_only_memory_logup.rs index e4f25c16c..3e90dc89d 100644 --- a/crypto/stark/src/examples/read_only_memory_logup.rs +++ b/crypto/stark/src/examples/read_only_memory_logup.rs @@ -360,7 +360,7 @@ where transition_constraints: Vec>>, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct LogReadOnlyPublicInputs where F: IsFFTField + Send + Sync, diff --git a/crypto/stark/src/examples/simple_addition.rs b/crypto/stark/src/examples/simple_addition.rs index 78f938838..a78e71eb2 100644 --- a/crypto/stark/src/examples/simple_addition.rs +++ b/crypto/stark/src/examples/simple_addition.rs @@ -83,7 +83,7 @@ where constraints: Vec>>, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct SimpleAdditionPublicInputs where F: IsFFTField, diff --git a/crypto/stark/src/examples/simple_fibonacci.rs b/crypto/stark/src/examples/simple_fibonacci.rs index a39064258..7e7dab641 100644 --- a/crypto/stark/src/examples/simple_fibonacci.rs +++ b/crypto/stark/src/examples/simple_fibonacci.rs @@ -82,7 +82,7 @@ where constraints: Vec>>, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct FibonacciPublicInputs where F: IsFFTField, diff --git a/crypto/stark/src/examples/simple_periodic_cols.rs b/crypto/stark/src/examples/simple_periodic_cols.rs index 70f5da3b4..0f5b74af8 100644 --- a/crypto/stark/src/examples/simple_periodic_cols.rs +++ b/crypto/stark/src/examples/simple_periodic_cols.rs @@ -100,7 +100,7 @@ where transition_constraints: Vec>>, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct SimplePeriodicPublicInputs where F: IsFFTField, diff --git a/crypto/stark/src/fri/fri_decommit.rs b/crypto/stark/src/fri/fri_decommit.rs index f398096d5..baab2cd4d 100644 --- a/crypto/stark/src/fri/fri_decommit.rs +++ b/crypto/stark/src/fri/fri_decommit.rs @@ -4,8 +4,7 @@ use math::field::traits::IsField; use crate::config::Commitment; -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -#[serde(bound = "")] +#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct FriDecommitment { pub layers_auth_paths: Vec>, pub layers_evaluations_sym: Vec>, diff --git a/crypto/stark/src/lib.rs b/crypto/stark/src/lib.rs index 87236c5f9..2b93f41ba 100644 --- a/crypto/stark/src/lib.rs +++ b/crypto/stark/src/lib.rs @@ -21,6 +21,7 @@ pub mod grinding; pub mod instruments; pub mod lookup; pub(crate) mod par; +pub mod profile_markers; pub mod proof; pub mod prover; pub mod r4_denoms; diff --git a/crypto/stark/src/lookup.rs b/crypto/stark/src/lookup.rs index 5174bf66c..8404e9ed3 100644 --- a/crypto/stark/src/lookup.rs +++ b/crypto/stark/src/lookup.rs @@ -1394,8 +1394,7 @@ impl BusInteraction { /// /// For the circular constraint, `table_contribution / N` is the per-row offset /// that makes the accumulated column wrap to zero at row N-1. -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -#[serde(bound = "")] +#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct BusPublicInputs where E: IsField, @@ -1403,20 +1402,45 @@ where /// Total sum of all LogUp terms across all rows (L). /// Used for bus balance check and to derive the per-row offset L/N. pub table_contribution: FieldElement, - /// Per-bus sums for this table (bus_id → sum) - for debug aggregation + /// Per-bus sums for this table (bus_id → sum) - for debug aggregation. + /// Debug-only aggregation state; not part of the archived proof (`Skip`). #[cfg(feature = "debug-checks")] + #[rkyv(with = rkyv::with::Skip)] pub per_bus_sums: HashMap>, /// Per-bus sender sums (bus_id → sum) - positive contributions #[cfg(feature = "debug-checks")] + #[rkyv(with = rkyv::with::Skip)] pub per_bus_sender_sums: HashMap>, /// Per-bus receiver sums (bus_id → sum) - absolute value (before negation) #[cfg(feature = "debug-checks")] + #[rkyv(with = rkyv::with::Skip)] pub per_bus_receiver_sums: HashMap>, /// Table name for debug output #[cfg(feature = "debug-checks")] + #[rkyv(with = rkyv::with::Skip)] pub table_name: String, } +impl BusPublicInputs { + /// Build a `BusPublicInputs` carrying just the table contribution `L`. + /// The debug-only per-bus aggregation fields are defaulted (empty). Used by + /// the zero-copy verifier, which reads only `table_contribution` from the + /// archived proof. + pub fn from_contribution(table_contribution: FieldElement) -> Self { + Self { + table_contribution, + #[cfg(feature = "debug-checks")] + per_bus_sums: HashMap::new(), + #[cfg(feature = "debug-checks")] + per_bus_sender_sums: HashMap::new(), + #[cfg(feature = "debug-checks")] + per_bus_receiver_sums: HashMap::new(), + #[cfg(feature = "debug-checks")] + table_name: String::new(), + } + } +} + /// Trait representing boundary constraint building behaviour. /// Should be defined when creating an `AirWithBuses` if the AIR requires its own boundary constraints aside from the lookup ones pub trait BoundaryConstraintBuilder< diff --git a/crypto/stark/src/profile_markers.rs b/crypto/stark/src/profile_markers.rs new file mode 100644 index 000000000..570b68641 --- /dev/null +++ b/crypto/stark/src/profile_markers.rs @@ -0,0 +1,27 @@ +//! Inlining-immune markers for guest-side step profiling. +//! +//! Each marker emits `addi x0, x0, N` on the RISC-V guest: a real instruction +//! (so it survives inlining and optimization, unlike a removed symbol) that +//! writes to the zero register and is otherwise a no-op. Real generated code +//! never emits `addi x0, x0, N` for any nonzero `N` spontaneously (`x0` is +//! hardwired to zero and writes to it are always discarded), so these values +//! can't collide with organic instructions. Do not reuse this immediate +//! encoding space for anything other than step markers. +//! +//! Kept separate from the `instruments` feature: `instruments` uses +//! `std::time::Instant::now()`, which panics on the guest target. + +pub const STEP_DECODE_DONE: u32 = 1; +pub const STEP_AIRS_AND_BUS_BALANCE_DONE: u32 = 2; +pub const STEP_REPLAY_ROUNDS_AFTER_ROUND_1: u32 = 3; +pub const STEP_VERIFY_CLAIMED_COMPOSITION_POLYNOMIAL: u32 = 4; +pub const STEP_VERIFY_FRI: u32 = 5; +pub const STEP_VERIFY_TRACE_AND_COMPOSITION_OPENINGS: u32 = 6; + +#[inline(always)] +pub fn step_marker() { + #[cfg(all(feature = "profile-markers", target_arch = "riscv64"))] + unsafe { + core::arch::asm!("addi x0, x0, {n}", n = const N); + } +} diff --git a/crypto/stark/src/proof/options.rs b/crypto/stark/src/proof/options.rs index 70976b993..2342d5a14 100644 --- a/crypto/stark/src/proof/options.rs +++ b/crypto/stark/src/proof/options.rs @@ -39,7 +39,7 @@ impl fmt::Display for ProofOptionsError { /// - `coset_offset`: the offset for the coset /// - `grinding_factor`: the number of leading zeros that we want for the Hash(hash || nonce) #[cfg_attr(feature = "wasm", wasm_bindgen)] -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct ProofOptions { pub blowup_factor: u8, pub fri_number_of_queries: usize, diff --git a/crypto/stark/src/proof/stark.rs b/crypto/stark/src/proof/stark.rs index 851c0b37a..4229b2bdb 100644 --- a/crypto/stark/src/proof/stark.rs +++ b/crypto/stark/src/proof/stark.rs @@ -8,8 +8,7 @@ use crate::{ config::Commitment, fri::fri_decommit::FriDecommitment, lookup::BusPublicInputs, table::Table, }; -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -#[serde(bound = "")] +#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] /// Opening of a bit-reversed, row-paired commitment at one FRI query. /// /// The queried row and its symmetric counterpart (LDE positions `2·iota`, @@ -22,8 +21,7 @@ pub struct PolynomialOpenings { pub evaluations_sym: Vec>, } -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -#[serde(bound = "")] +#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct DeepPolynomialOpening, E: IsField> { pub composition_poly: PolynomialOpenings, pub main_trace_polys: PolynomialOpenings, @@ -35,8 +33,7 @@ pub struct DeepPolynomialOpening, E: IsField> { pub type DeepPolynomialOpenings = Vec>; -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -#[serde(bound = "PI: serde::Serialize + serde::de::DeserializeOwned")] +#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct StarkProof, E: IsField, PI> { // Length of the execution trace pub trace_length: usize, @@ -78,8 +75,7 @@ pub struct StarkProof, E: IsField, PI> { /// A collection of STARK proofs for multiple AIRs. /// Used for multi-table proving where tables are linked via bus (LogUp). /// Returned by `Prover::multi_prove` and verified by `Verifier::multi_verify`. -#[derive(Debug, serde::Serialize, serde::Deserialize)] -#[serde(bound = "PI: serde::Serialize + serde::de::DeserializeOwned")] +#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct MultiProof, E: IsField, PI> { pub proofs: Vec>, } diff --git a/crypto/stark/src/table.rs b/crypto/stark/src/table.rs index dc188d690..93541c65b 100644 --- a/crypto/stark/src/table.rs +++ b/crypto/stark/src/table.rs @@ -41,12 +41,18 @@ impl std::fmt::Debug for TableMmapBacking { /// the STARK protocol implementation, such as the `TraceTable` and the `EvaluationFrame`. /// Since this struct is a representation of a two-dimensional table, all rows should have the same /// length. -#[derive(Default, Debug, serde::Deserialize)] +#[derive(Default, Debug)] #[cfg_attr( not(feature = "disk-spill"), - derive(serde::Serialize, Clone, PartialEq, Eq) + derive( + Clone, + PartialEq, + Eq, + rkyv::Archive, + rkyv::Serialize, + rkyv::Deserialize + ) )] -#[serde(bound = "")] pub struct Table { /// Row-major backing store. Crate-private: external callers must go through /// the spill-safe accessors (`get`/`get_row`/`set`) rather than indexing the @@ -55,47 +61,140 @@ pub struct Table { pub width: usize, pub height: usize, #[cfg(feature = "disk-spill")] - #[serde(skip)] pub(crate) mmap_backing: Option, } +// Manual rkyv impl under disk-spill: the derive can't handle `mmap_backing`, +// and serialization must read through `row_major_data()` so a spilled table +// archives its mmap contents (deserializing always yields an unspilled table). +// The archived layout matches what the derive generates without disk-spill, so +// both configurations produce byte-identical archives. #[cfg(feature = "disk-spill")] -impl serde::Serialize for Table -where - FieldElement: serde::Serialize, -{ - fn serialize(&self, serializer: S) -> Result { - use serde::ser::SerializeStruct; - let mut s = serializer.serialize_struct("Table", 3)?; - if self.mmap_backing.is_some() { - s.serialize_field("data", &MmapDataSeq(self))?; - } else { - s.serialize_field("data", &self.data)?; +mod archived_table { + use super::{FieldElement, IsField, Table}; + use math::field::element::ArchivedFieldElement; + use rkyv::rancor::Fallible; + use rkyv::ser::{Allocator, Writer}; + use rkyv::vec::{ArchivedVec, VecResolver}; + use rkyv::{Archive, Deserialize, Place, Portable, Serialize}; + + #[derive(Portable, rkyv::bytecheck::CheckBytes)] + #[bytecheck(crate = rkyv::bytecheck)] + #[repr(C)] + pub struct ArchivedTable + where + F::BaseType: Archive, + { + pub data: ArchivedVec>, + pub width: rkyv::primitive::ArchivedUsize, + pub height: rkyv::primitive::ArchivedUsize, + } + + pub struct TableResolver { + data: VecResolver, + } + + impl Archive for Table + where + F::BaseType: Archive, + { + type Archived = ArchivedTable; + type Resolver = TableResolver; + + fn resolve(&self, resolver: Self::Resolver, out: Place) { + rkyv::munge::munge!(let ArchivedTable { data, width, height } = out); + ArchivedVec::resolve_from_len(self.width * self.height, resolver.data, data); + self.width.resolve((), width); + self.height.resolve((), height); + } + } + + impl Serialize for Table + where + F::BaseType: Archive, + FieldElement: Serialize, + S: Fallible + Allocator + Writer + ?Sized, + { + fn serialize(&self, serializer: &mut S) -> Result { + Ok(TableResolver { + data: ArchivedVec::serialize_from_slice(self.row_major_data(), serializer)?, + }) + } + } + + impl Deserialize, D> for ArchivedTable + where + F::BaseType: Archive, + ArchivedFieldElement: Deserialize, D>, + D: Fallible + ?Sized, + { + fn deserialize(&self, deserializer: &mut D) -> Result, D::Error> { + Ok(Table { + data: self.data.deserialize(deserializer)?, + width: self.width.to_native() as usize, + height: self.height.to_native() as usize, + mmap_backing: None, + }) } - s.serialize_field("width", &self.width)?; - s.serialize_field("height", &self.height)?; - s.end() } } #[cfg(feature = "disk-spill")] -struct MmapDataSeq<'a, F: IsField>(&'a Table); +pub use archived_table::ArchivedTable; -#[cfg(feature = "disk-spill")] -impl serde::Serialize for MmapDataSeq<'_, F> +/// Read API over an rkyv-archived [`Table`], used by the verifier to consume +/// the out-of-domain evaluations straight from the proof buffer. On +/// little-endian targets the element data is viewed in place with no copy. +#[cfg(target_endian = "little")] +impl ArchivedTable where - FieldElement: serde::Serialize, + F::BaseType: rkyv::Archive, { - fn serialize(&self, serializer: S) -> Result { - use serde::ser::SerializeSeq; - let table = self.0; - let mut seq = serializer.serialize_seq(Some(table.width * table.height))?; - for r in 0..table.height { - for elem in table.get_row(r) { - seq.serialize_element(elem)?; - } - } - seq.end() + #[inline] + pub fn width(&self) -> usize { + self.width.to_native() as usize + } + + #[inline] + pub fn height(&self) -> usize { + self.height.to_native() as usize + } + + /// Full row-major element data, viewed in place. + #[inline] + pub fn row_major_data(&self) -> &[FieldElement] { + math::field::element::ArchivedFieldElement::slice_as_native(self.data.as_slice()) + } + + /// `true` iff the backing data holds exactly `width × height` elements — + /// the invariant `get_row` indexing relies on. A malformed archive can + /// advertise dimensions that disagree with the data length; callers must + /// reject such tables before row access. + #[inline] + pub fn dimensions_consistent(&self) -> bool { + self.width() + .checked_mul(self.height()) + .is_some_and(|n| n == self.data.len()) + } + + /// Row `row_idx` as a native field-element slice (no copy). + #[inline] + pub fn get_row(&self, row_idx: usize) -> &[FieldElement] { + let width = self.width(); + let start = row_idx * width; + &self.row_major_data()[start..start + width] + } + + /// Build a [`Frame`] over this table, identical to [`Table::into_frame`]. + /// Only the small OOD frame is materialized (bounded by `step_size × width`), + /// never the whole proof. + pub fn into_frame(&self, main_trace_columns: usize, step_size: usize) -> Frame + where + F: IsSubFieldOf, + { + frame_from_rows(self.height(), step_size, main_trace_columns, |row_idx| { + self.get_row(row_idx) + }) } } @@ -361,29 +460,49 @@ impl Table { /// Given a step size, converts the given table into a `Frame`. /// Clones row data into owned Vecs (only used by verifier on small OOD tables). pub fn into_frame(&self, main_trace_columns: usize, step_size: usize) -> Frame { - debug_assert!(self.height.is_multiple_of(step_size)); - let steps = (0..self.height) - .step_by(step_size) - .map(|initial_row_idx| { - let end_row_idx = initial_row_idx + step_size; - - let mut step_main_data: Vec>> = Vec::new(); - let mut step_aux_data: Vec>> = Vec::new(); - - (initial_row_idx..end_row_idx).for_each(|row_idx| { - let row = self.get_row(row_idx); - step_main_data.push(row[..main_trace_columns].to_vec()); - step_aux_data.push(row[main_trace_columns..].to_vec()); - }); - - TableView::new(step_main_data, step_aux_data) - }) - .collect(); - - Frame::new(steps) + frame_from_rows(self.height, step_size, main_trace_columns, |row_idx| { + self.get_row(row_idx) + }) } } +/// Build a [`Frame`] from `height` rows accessed via `get_row`, splitting each +/// row at `main_trace_columns` into main/aux. Shared by [`Table::into_frame`] +/// and the zero-copy `OodTableRef::into_frame` so both produce identical frames. +/// +/// Only the small out-of-domain frame is materialized here (bounded by +/// `step_size × width`), never the full trace. +pub fn frame_from_rows<'a, F>( + height: usize, + step_size: usize, + main_trace_columns: usize, + get_row: impl Fn(usize) -> &'a [FieldElement], +) -> Frame +where + F: IsSubFieldOf + IsField + 'a, +{ + debug_assert!(height.is_multiple_of(step_size)); + let steps = (0..height) + .step_by(step_size) + .map(|initial_row_idx| { + let end_row_idx = initial_row_idx + step_size; + + let mut step_main_data: Vec>> = Vec::new(); + let mut step_aux_data: Vec>> = Vec::new(); + + (initial_row_idx..end_row_idx).for_each(|row_idx| { + let row = get_row(row_idx); + step_main_data.push(row[..main_trace_columns].to_vec()); + step_aux_data.push(row[main_trace_columns..].to_vec()); + }); + + TableView::new(step_main_data, step_aux_data) + }) + .collect(); + + Frame::new(steps) +} + /// A view of a contiguous subset of rows of a table. /// /// Owns its row data (Vec per row) so it can be built from either row-major Tables diff --git a/crypto/stark/src/tests/bus_tests/completeness_tests.rs b/crypto/stark/src/tests/bus_tests/completeness_tests.rs index 83f8ac391..51f6ef50c 100644 --- a/crypto/stark/src/tests/bus_tests/completeness_tests.rs +++ b/crypto/stark/src/tests/bus_tests/completeness_tests.rs @@ -377,9 +377,10 @@ fn test_serialization_roundtrip() { multi_prove_ram(air_trace_pairs, &mut DefaultTranscript::::new(&[])).unwrap(); // Serialize and deserialize - let serialized = serde_cbor::to_vec(&multi_proof).expect("serialization failed"); + let serialized = + rkyv::to_bytes::(&multi_proof).expect("serialization failed"); let deserialized: crate::proof::stark::MultiProof = - serde_cbor::from_slice(&serialized).expect("deserialization failed"); + rkyv::from_bytes::<_, rkyv::rancor::Error>(&serialized).expect("deserialization failed"); let airs: Vec<&dyn AIR> = vec![&cpu_air, &add_air, &mul_air]; diff --git a/crypto/stark/src/tests/prove_verify_roundtrip_tests.rs b/crypto/stark/src/tests/prove_verify_roundtrip_tests.rs index 4059ed481..032e68ea1 100644 --- a/crypto/stark/src/tests/prove_verify_roundtrip_tests.rs +++ b/crypto/stark/src/tests/prove_verify_roundtrip_tests.rs @@ -142,13 +142,14 @@ fn test_verify_serialized_multi_table_proofs() { // NETWORK TRANSMISSION - Serialize and deserialize (using CBOR binary format) // ========================================================================= - let serialized = serde_cbor::to_vec(&proofs).expect("Failed to serialize proofs"); + let serialized = + rkyv::to_bytes::(&proofs).expect("Failed to serialize proofs"); // At this point, the prover's data is dropped (out of scope above) // The verifier only has the serialized data let received_proofs: MultiProof = - serde_cbor::from_slice(&serialized).expect("Failed to deserialize proofs"); + rkyv::from_bytes::<_, rkyv::rancor::Error>(&serialized).expect("Failed to deserialize proofs"); // ========================================================================= // VERIFIER SIDE - Reconstruct AIRs and verify diff --git a/crypto/stark/src/verifier.rs b/crypto/stark/src/verifier.rs index 03119f617..0cf5eb460 100644 --- a/crypto/stark/src/verifier.rs +++ b/crypto/stark/src/verifier.rs @@ -1,7 +1,7 @@ use super::{ config::BatchedMerkleTreeBackend, domain::VerifierDomain, - fri::fri_decommit::FriDecommitment, + fri::fri_decommit::ArchivedFriDecommitment, grinding, proof::stark::StarkProof, traits::{AIR, TransitionEvaluationContext}, @@ -9,10 +9,15 @@ use super::{ use crate::{ config::Commitment, domain::new_verifier_domain, - lookup::{LOGUP_CHALLENGE_ALPHA, LOGUP_NUM_CHALLENGES, PackingShifts, compute_alpha_powers}, - proof::stark::{DeepPolynomialOpening, MultiProof, PolynomialOpenings}, + lookup::{BusPublicInputs, LOGUP_CHALLENGE_ALPHA, LOGUP_NUM_CHALLENGES, PackingShifts, compute_alpha_powers}, + proof::stark::{ + ArchivedDeepPolynomialOpening, ArchivedMultiProof, ArchivedPolynomialOpenings, + ArchivedStarkProof, MultiProof, + }, }; -use crypto::{fiat_shamir::is_transcript::IsStarkTranscript, merkle_tree::proof::Proof}; +use crypto::fiat_shamir::is_transcript::IsStarkTranscript; +use crypto::merkle_tree::proof::verify_merkle_path; +use math::field::element::ArchivedFieldElement; #[cfg(not(feature = "test_fiat_shamir"))] use log::error; #[cfg(feature = "debug-checks")] @@ -44,6 +49,11 @@ impl< FieldExtension: IsField + Send + Sync, PI, > IsStarkVerifier for Verifier +where + Field::BaseType: rkyv::Archive, + FieldExtension::BaseType: rkyv::Archive, + PI: rkyv::Archive, + ::Archived: rkyv::Deserialize, { } @@ -75,6 +85,23 @@ where pub type DeepPolynomialEvaluations = (Vec>, Vec>); +// The verifier reads proofs in place from their rkyv archive; archived field +// elements are viewed as native ones, which is only valid on little-endian. +#[cfg(not(target_endian = "little"))] +compile_error!("the zero-copy STARK verifier requires a little-endian target"); + +/// Deserializer used to materialize the (tiny) per-proof `PI` public inputs. +pub type PiDeserializer = rkyv::api::high::HighDeserializer; + +/// `&[FieldElement]` view over an archived field-element vector (no copy). +#[inline] +fn evals(v: &rkyv::vec::ArchivedVec>) -> &[FieldElement] +where + G::BaseType: rkyv::Archive, +{ + ArchivedFieldElement::slice_as_native(v.as_slice()) +} + /// The functionality of a STARK verifier providing methods to run the STARK Verify protocol /// https://lambdaclass.github.io/lambdaworks/starks/protocol.html pub trait IsStarkVerifier< @@ -82,6 +109,11 @@ pub trait IsStarkVerifier< FieldExtension: Send + Sync + IsField, PI, > +where + Field::BaseType: rkyv::Archive, + FieldExtension::BaseType: rkyv::Archive, + PI: rkyv::Archive, + ::Archived: rkyv::Deserialize, { fn sample_query_indexes( number_of_queries: usize, @@ -99,15 +131,25 @@ pub trait IsStarkVerifier< /// See https://lambdaclass.github.io/lambdaworks/starks/protocol.html#step-2-verify-claimed-composition-polynomial fn step_2_verify_claimed_composition_polynomial( air: &dyn AIR, - proof: &StarkProof, + proof: &ArchivedStarkProof, + public_inputs: &PI, domain: &VerifierDomain, challenges: &Challenges, ) -> bool { - let trace_length = proof.trace_length; + crate::profile_markers::step_marker::< + { crate::profile_markers::STEP_VERIFY_CLAIMED_COMPOSITION_POLYNOMIAL }, + >(); + let trace_length = proof.trace_length.to_native() as usize; + // Owned `BusPublicInputs` (just the table contribution L — one field + // element) reconstructed from the archive for the AIR boundary call. + let bus_public_inputs = proof + .bus_public_inputs + .as_ref() + .map(|bpi| BusPublicInputs::from_contribution(bpi.table_contribution.as_native().clone())); let boundary_constraints = air.boundary_constraints( - &proof.public_inputs, + public_inputs, &challenges.rap_challenges, - proof.bus_public_inputs.as_ref(), + bus_public_inputs.as_ref(), trace_length, ); // Precompute g^step once per distinct step to avoid the prior O(B^2) @@ -170,8 +212,16 @@ pub trait IsStarkVerifier< .map(|poly| poly.evaluate(&challenges.z)) .collect::>>(); - let num_main_trace_columns = - proof.trace_ood_evaluations.width - air.num_auxiliary_rap_columns(); + // A malformed archive can advertise fewer OOD columns than the AIR's + // aux count; reject instead of underflowing. + let num_main_trace_columns = match proof + .trace_ood_evaluations + .width() + .checked_sub(air.num_auxiliary_rap_columns()) + { + Some(n) => n, + None => return false, + }; let logup_alpha_powers: Vec> = if challenges.rap_challenges.len() > LOGUP_CHALLENGE_ALPHA { @@ -183,11 +233,11 @@ pub trait IsStarkVerifier< Vec::new() }; - let logup_table_offset = match &proof.bus_public_inputs { + let logup_table_offset = match proof.bus_public_inputs.as_ref() { Some(bpi) => { let n = FieldElement::::from(trace_length as u64); match n.inv() { - Ok(n_inv) => n_inv * &bpi.table_contribution, + Ok(n_inv) => n_inv * bpi.table_contribution.as_native(), Err(_) => return false, // trace_length == 0 is invalid } } @@ -227,8 +277,7 @@ pub trait IsStarkVerifier< let composition_poly_ood_evaluation = &boundary_quotient_ood_evaluation + transition_c_i_evaluations_sum; - let composition_poly_claimed_ood_evaluation = proof - .composition_poly_parts_ood_evaluation + let composition_poly_claimed_ood_evaluation = evals(&proof.composition_poly_parts_ood_evaluation) .iter() .rev() .fold(FieldElement::zero(), |acc, coeff| { @@ -242,7 +291,7 @@ pub trait IsStarkVerifier< /// openings of the trace polynomials and the composition polynomial parts. It then uses these to verify that the /// FRI decommitments are valid and correspond to the Deep composition polynomial. fn step_3_verify_fri( - proof: &StarkProof, + proof: &ArchivedStarkProof, domain: &VerifierDomain, challenges: &Challenges, ) -> bool @@ -250,6 +299,7 @@ pub trait IsStarkVerifier< FieldElement: AsBytes + Sync + Send, FieldElement: AsBytes + Sync + Send, { + crate::profile_markers::step_marker::<{ crate::profile_markers::STEP_VERIFY_FRI }>(); let (deep_poly_evaluations, deep_poly_evaluations_sym) = match Self::reconstruct_deep_composition_poly_evaluations_for_all_queries( challenges, domain, proof, @@ -271,6 +321,7 @@ pub trait IsStarkVerifier< proof .query_list + .as_slice() .iter() .zip(&challenges.iotas) .zip(evaluation_point_inverse) @@ -307,7 +358,7 @@ pub trait IsStarkVerifier< /// `evaluations ‖ evaluations_sym` and verify once. (Same as the composition /// opening check.) fn verify_opening_pair( - opening: &PolynomialOpenings, + opening: &ArchivedPolynomialOpenings, root: &Commitment, iota: usize, ) -> bool @@ -315,20 +366,24 @@ pub trait IsStarkVerifier< FieldElement: AsBytes + Sync + Send, FieldElement: AsBytes + Sync + Send, E: IsField, + E::BaseType: rkyv::Archive, Field: IsSubFieldOf, { - let mut value = opening.evaluations.clone(); - value.extend_from_slice(&opening.evaluations_sym); - opening - .proof - .verify::>(root, iota, &value) + let mut value = evals(&opening.evaluations).to_vec(); + value.extend_from_slice(evals(&opening.evaluations_sym)); + verify_merkle_path::>( + opening.proof.merkle_path.as_slice(), + root, + iota, + &value, + ) } /// Verify opening Open(tⱼ(D_LDE), 𝜐) and Open(tⱼ(D_LDE), -𝜐) for all trace polynomials tⱼ, /// where 𝜐 and -𝜐 are the elements corresponding to the index challenge `iota`. fn verify_trace_openings( - proof: &StarkProof, - deep_poly_openings: &DeepPolynomialOpening, + proof: &ArchivedStarkProof, + deep_poly_openings: &ArchivedDeepPolynomialOpening, iota: usize, ) -> bool where @@ -346,8 +401,8 @@ pub trait IsStarkVerifier< // unreachable in practice (multi_verify rejects such proofs upstream), // but a defensive check keeps this function self-contained. ok &= match ( - &proof.lde_trace_precomputed_merkle_root, - &deep_poly_openings.precomputed_trace_polys, + proof.lde_trace_precomputed_merkle_root.as_ref(), + deep_poly_openings.precomputed_trace_polys.as_ref(), ) { (Some(root), Some(opening)) => Self::verify_opening_pair::(opening, root, iota), (None, None) => true, @@ -356,11 +411,11 @@ pub trait IsStarkVerifier< // Auxiliary trace. ok &= match ( - proof.lde_trace_aux_merkle_root, - &deep_poly_openings.aux_trace_polys, + proof.lde_trace_aux_merkle_root.as_ref(), + deep_poly_openings.aux_trace_polys.as_ref(), ) { (Some(root), Some(opening)) => { - Self::verify_opening_pair::(opening, &root, iota) + Self::verify_opening_pair::(opening, root, iota) } (None, None) => true, _ => false, @@ -372,7 +427,7 @@ pub trait IsStarkVerifier< /// Verify opening Open(Hᵢ(D_LDE), 𝜐) and Open(Hᵢ(D_LDE), -𝜐) for all parts Hᵢof the composition /// polynomial, where 𝜐 and -𝜐 are the elements corresponding to the index challenge `iota`. fn verify_composition_poly_opening( - deep_poly_openings: &DeepPolynomialOpening, + deep_poly_openings: &ArchivedDeepPolynomialOpening, composition_poly_merkle_root: &Commitment, iota: &usize, ) -> bool @@ -380,34 +435,35 @@ pub trait IsStarkVerifier< FieldElement: AsBytes + Sync + Send, FieldElement: AsBytes + Sync + Send, { - let mut value = deep_poly_openings.composition_poly.evaluations.clone(); - value.extend_from_slice(&deep_poly_openings.composition_poly.evaluations_sym); - - deep_poly_openings - .composition_poly - .proof - .verify::>( - composition_poly_merkle_root, - *iota, - &value, - ) + let mut value = evals(&deep_poly_openings.composition_poly.evaluations).to_vec(); + value.extend_from_slice(evals(&deep_poly_openings.composition_poly.evaluations_sym)); + + verify_merkle_path::>( + deep_poly_openings.composition_poly.proof.merkle_path.as_slice(), + composition_poly_merkle_root, + *iota, + &value, + ) } /// Verifies the validity of the purported values of the trace polynomials and the composition polynomial /// parts at the domain elements and their symmetric counterparts corresponding to all the FRI query /// index challenges. fn step_4_verify_trace_and_composition_openings( - proof: &StarkProof, + proof: &ArchivedStarkProof, challenges: &Challenges, ) -> bool where FieldElement: AsBytes + Sync + Send, FieldElement: AsBytes + Sync + Send, { + crate::profile_markers::step_marker::< + { crate::profile_markers::STEP_VERIFY_TRACE_AND_COMPOSITION_OPENINGS }, + >(); challenges .iotas .iter() - .zip(&proof.deep_poly_openings) + .zip(proof.deep_poly_openings.as_slice()) .all(|(iota_n, deep_poly_opening)| { Self::verify_composition_poly_opening( deep_poly_opening, @@ -420,7 +476,7 @@ pub trait IsStarkVerifier< /// Verifies the openings of a fold polynomial of an inner layer of FRI. fn verify_fri_layer_openings( merkle_root: &Commitment, - auth_path_sym: &Proof, + auth_path_sym: &[Commitment], evaluation: &FieldElement, evaluation_sym: &FieldElement, iota: usize, @@ -435,7 +491,8 @@ pub trait IsStarkVerifier< vec![evaluation.clone(), evaluation_sym.clone()] }; - auth_path_sym.verify::>( + verify_merkle_path::>( + auth_path_sym, merkle_root, iota >> 1, &evaluations, @@ -451,10 +508,10 @@ pub trait IsStarkVerifier< /// `deep_composition_evaluation`: precomputed value of p₀(𝜐), where p₀ is the deep composition polynomial. /// `deep_composition_evaluation_sym`: precomputed value of p₀(-𝜐), where p₀ is the deep composition polynomial. fn verify_query_and_sym_openings( - proof: &StarkProof, + proof: &ArchivedStarkProof, zetas: &[FieldElement], iota: usize, - fri_decommitment: &FriDecommitment, + fri_decommitment: &ArchivedFriDecommitment, evaluation_point_inv: FieldElement, deep_composition_evaluation: &FieldElement, deep_composition_evaluation_sym: &FieldElement, @@ -463,7 +520,19 @@ pub trait IsStarkVerifier< FieldElement: AsBytes + Sync + Send, FieldElement: AsBytes + Sync + Send, { - let fri_layers_merkle_roots = &proof.fri_layers_merkle_roots; + let fri_layers_merkle_roots = proof.fri_layers_merkle_roots.as_slice(); + let fri_last_value = proof.fri_last_value.as_native(); + + // The three per-layer vectors must line up with the committed roots: + // the fold below iterates their zip (min length) and only compares the + // final value on the last evaluation index, so an over-long + // `layers_evaluations_sym` would otherwise skip the `fri_last_value` + // check entirely. + if fri_decommitment.layers_auth_paths.len() != fri_layers_merkle_roots.len() + || fri_decommitment.layers_evaluations_sym.len() != fri_layers_merkle_roots.len() + { + return false; + } let evaluation_point_vec: Vec> = core::iter::successors(Some(evaluation_point_inv.square()), |evaluation_point| { Some(evaluation_point.square()) @@ -483,7 +552,7 @@ pub trait IsStarkVerifier< // In this case, the fold loop below doesn't iterate, so we need to verify // the final value directly here. if fri_layers_merkle_roots.is_empty() { - return v == proof.fri_last_value; + return v == *fri_last_value; } // For each FRI layer, starting from the layer 1: use the proof to verify the validity of values pᵢ(−𝜐^(2ⁱ)) (given by the prover) and @@ -492,8 +561,8 @@ pub trait IsStarkVerifier< fri_layers_merkle_roots .iter() .enumerate() - .zip(&fri_decommitment.layers_auth_paths) - .zip(&fri_decommitment.layers_evaluations_sym) + .zip(fri_decommitment.layers_auth_paths.as_slice()) + .zip(evals(&fri_decommitment.layers_evaluations_sym)) .zip(evaluation_point_vec) .fold( true, @@ -507,7 +576,7 @@ pub trait IsStarkVerifier< // `evaluation_sym` is pᵢ(−𝜐^(2ⁱ)). let openings_ok = Self::verify_fri_layer_openings( merkle_root, - auth_path_sym, + auth_path_sym.merkle_path.as_slice(), &v, evaluation_sym, index, @@ -525,7 +594,7 @@ pub trait IsStarkVerifier< result & openings_ok } else { // Check that final value is the given by the prover - result & (v == proof.fri_last_value) & openings_ok + result & (v == *fri_last_value) & openings_ok } }, ) @@ -534,8 +603,13 @@ pub trait IsStarkVerifier< fn reconstruct_deep_composition_poly_evaluations_for_all_queries( challenges: &Challenges, domain: &VerifierDomain, - proof: &StarkProof, + proof: &ArchivedStarkProof, ) -> Option> { + // A malformed proof can carry fewer openings than query challenges; + // reject instead of panicking on the indexed access below. + if proof.deep_poly_openings.len() < challenges.iotas.len() { + return None; + } let num_queries = challenges.iotas.len(); let mut deep_poly_evaluations = Vec::with_capacity(num_queries); let mut deep_poly_evaluations_sym = Vec::with_capacity(num_queries); @@ -547,78 +621,103 @@ pub trait IsStarkVerifier< .expect("verifier domain root_order is a valid power of two"); for (i, iota) in challenges.iotas.iter().enumerate() { - let opening = &proof.deep_poly_openings[i]; + let opening = &proof.deep_poly_openings.as_slice()[i]; // Base-field portion: precomputed columns FIRST, then main trace columns. let mut lde_base: Vec> = Vec::new(); - if let Some(p) = &opening.precomputed_trace_polys { - lde_base.extend_from_slice(&p.evaluations); + if let Some(p) = opening.precomputed_trace_polys.as_ref() { + lde_base.extend_from_slice(evals(&p.evaluations)); } - lde_base.extend_from_slice(&opening.main_trace_polys.evaluations); + lde_base.extend_from_slice(evals(&opening.main_trace_polys.evaluations)); let lde_aux: &[FieldElement] = opening .aux_trace_polys .as_ref() - .map(|a| a.evaluations.as_slice()) + .map(|a| evals(&a.evaluations)) .unwrap_or(&[]); - let evaluation_point = Self::query_challenge_to_evaluation_point(*iota, false, domain); - deep_poly_evaluations.push(Self::reconstruct_deep_composition_poly_evaluation( - proof, - &evaluation_point, - primitive_root, - challenges, - &lde_base, - lde_aux, - &opening.composition_poly.evaluations, - )?); - - // Mirror for the symmetric query point. let mut lde_base_sym: Vec> = Vec::new(); - if let Some(p) = &opening.precomputed_trace_polys { - lde_base_sym.extend_from_slice(&p.evaluations_sym); + if let Some(p) = opening.precomputed_trace_polys.as_ref() { + lde_base_sym.extend_from_slice(evals(&p.evaluations_sym)); } - lde_base_sym.extend_from_slice(&opening.main_trace_polys.evaluations_sym); + lde_base_sym.extend_from_slice(evals(&opening.main_trace_polys.evaluations_sym)); let lde_aux_sym: &[FieldElement] = opening .aux_trace_polys .as_ref() - .map(|a| a.evaluations_sym.as_slice()) + .map(|a| evals(&a.evaluations_sym)) .unwrap_or(&[]); - let evaluation_point = Self::query_challenge_to_evaluation_point(*iota, true, domain); - deep_poly_evaluations_sym.push(Self::reconstruct_deep_composition_poly_evaluation( - proof, - &evaluation_point, - primitive_root, - challenges, - &lde_base_sym, - lde_aux_sym, - &opening.composition_poly.evaluations_sym, - )?); + let evaluation_point = Self::query_challenge_to_evaluation_point(*iota, false, domain); + let evaluation_point_sym = + Self::query_challenge_to_evaluation_point(*iota, true, domain); + let (evaluation, evaluation_sym) = + Self::reconstruct_deep_composition_poly_evaluation_pair( + proof, + &evaluation_point, + &evaluation_point_sym, + primitive_root, + challenges, + &lde_base, + lde_aux, + evals(&opening.composition_poly.evaluations), + &lde_base_sym, + lde_aux_sym, + evals(&opening.composition_poly.evaluations_sym), + )?; + deep_poly_evaluations.push(evaluation); + deep_poly_evaluations_sym.push(evaluation_sym); } Some((deep_poly_evaluations, deep_poly_evaluations_sym)) } - fn reconstruct_deep_composition_poly_evaluation( - proof: &StarkProof, + /// Reconstructs the deep composition polynomial evaluation at a query's + /// evaluation point and its symmetric counterpart in one traversal. The + /// OOD table walk and `trace_term_coeffs` walk are shared between the two + /// (they don't depend on the evaluation point), halving the loads and + /// letting the two `denoms_trace` vectors, and the two composition-tail + /// denominators, share a single batch-inverse call each. + /// + /// Algebraic rewrite of the per-element trace term: for column `col` and + /// row `row`, + /// coeff(col,row) * (base(col) - ood(row,col)) * denom(row) + /// distributes to + /// denom(row) * (coeff(col,row) * base(col) - coeff(col,row) * ood(row,col)). + /// Summing over `col` first isolates `coeff * ood`, which is identical for + /// both evaluation points, from `coeff * base`, which differs; and moves + /// the `denom(row)` multiplication (ext * ext) out of the column loop. + fn reconstruct_deep_composition_poly_evaluation_pair( + proof: &ArchivedStarkProof, evaluation_point: &FieldElement, + evaluation_point_sym: &FieldElement, primitive_root: &FieldElement, challenges: &Challenges, lde_trace_base_evaluations: &[FieldElement], lde_trace_aux_evaluations: &[FieldElement], lde_composition_poly_parts_evaluation: &[FieldElement], - ) -> Option> { - let ood_evaluations_table_height = proof.trace_ood_evaluations.height; - let ood_evaluations_table_width = proof.trace_ood_evaluations.width; + lde_trace_base_evaluations_sym: &[FieldElement], + lde_trace_aux_evaluations_sym: &[FieldElement], + lde_composition_poly_parts_evaluation_sym: &[FieldElement], + ) -> Option<(FieldElement, FieldElement)> { + let ood_evaluations_table_height = proof.trace_ood_evaluations.height(); + let ood_evaluations_table_width = proof.trace_ood_evaluations.width(); + // Hot loop below: resolve the archived OOD data to one flat slice once + // instead of re-deriving a row slice per element. + let ood_data = proof.trace_ood_evaluations.row_major_data(); let trace_term_coeffs = &challenges.trace_term_coeffs; - // Runtime guard: a malformed proof may supply opening evaluations whose - // column count does not match the OOD table width, or whose composition - // poly parts count does not match the proof's `composition_poly_parts_ood_evaluation`. - // Without these checks the indexing below would panic in release builds. - if lde_trace_base_evaluations.len() + lde_trace_aux_evaluations.len() - != ood_evaluations_table_width + let num_base = lde_trace_base_evaluations.len(); + // Runtime guards: a malformed proof may supply opening evaluations + // whose column count does not match the OOD table width, whose + // regular/symmetric base-column split disagree, or whose composition + // poly parts count does not match the proof's + // `composition_poly_parts_ood_evaluation`. Without these checks the + // indexing below would panic in release builds. + if num_base != lde_trace_base_evaluations_sym.len() { + return None; + } + if num_base + lde_trace_aux_evaluations.len() != ood_evaluations_table_width + || num_base + lde_trace_aux_evaluations_sym.len() != ood_evaluations_table_width { return None; } @@ -629,53 +728,87 @@ pub trait IsStarkVerifier< return None; } - let mut denoms_trace = Vec::with_capacity(ood_evaluations_table_height); + // Build both denominator sets in one walk of the shared z*g^k ladder, + // interleaved (regular, symmetric, regular, symmetric, ...), and + // invert them together in a single batch. + let mut denoms = Vec::with_capacity(2 * ood_evaluations_table_height); let mut current_z = challenges.z.clone(); for _ in 0..ood_evaluations_table_height { - denoms_trace.push(evaluation_point - ¤t_z); + denoms.push(evaluation_point - ¤t_z); + denoms.push(evaluation_point_sym - ¤t_z); current_z = primitive_root * ¤t_z; } // A malformed proof can land an OOD evaluation point on the LDE coset, reject. - FieldElement::inplace_batch_inverse(&mut denoms_trace).ok()?; - - let num_base = lde_trace_base_evaluations.len(); - let trace_term = (0..ood_evaluations_table_width) - .zip(&challenges.trace_term_coeffs) - .fold(FieldElement::zero(), |trace_terms, (col_idx, coeff_row)| { - let trace_i = (0..ood_evaluations_table_height).zip(coeff_row).fold( - FieldElement::zero(), - |trace_t, (row_idx, coeff)| { - let ood_val = &proof.trace_ood_evaluations.get_row(row_idx)[col_idx]; - // Stay in base when we can: F: IsSubFieldOf gives F - E -> E. - let diff: FieldElement = if col_idx < num_base { - &lde_trace_base_evaluations[col_idx] - ood_val - } else { - &lde_trace_aux_evaluations[col_idx - num_base] - ood_val - }; - let poly_evaluation = diff * &denoms_trace[row_idx]; - trace_t + &poly_evaluation * coeff - }, - ); - trace_terms + trace_i - }); + FieldElement::inplace_batch_inverse(&mut denoms).ok()?; + + let mut trace_term = FieldElement::::zero(); + let mut trace_term_sym = FieldElement::::zero(); + for row_idx in 0..ood_evaluations_table_height { + let ood_row = &ood_data[row_idx * ood_evaluations_table_width + ..(row_idx + 1) * ood_evaluations_table_width]; + let mut ood_row_sum = FieldElement::::zero(); + let mut base_row_sum = FieldElement::::zero(); + let mut base_row_sum_sym = FieldElement::::zero(); + for col_idx in 0..ood_evaluations_table_width { + let coeff = &trace_term_coeffs[col_idx][row_idx]; + let ood_val = &ood_row[col_idx]; + ood_row_sum = ood_row_sum + coeff * ood_val; + if col_idx < num_base { + // F: IsSubFieldOf gives the cheap asymmetric F * E -> E product. + base_row_sum = base_row_sum + &lde_trace_base_evaluations[col_idx] * coeff; + base_row_sum_sym = + base_row_sum_sym + &lde_trace_base_evaluations_sym[col_idx] * coeff; + } else { + let aux_idx = col_idx - num_base; + base_row_sum = base_row_sum + coeff * &lde_trace_aux_evaluations[aux_idx]; + base_row_sum_sym = + base_row_sum_sym + coeff * &lde_trace_aux_evaluations_sym[aux_idx]; + } + } + trace_term = trace_term + &denoms[2 * row_idx] * &(&base_row_sum - &ood_row_sum); + trace_term_sym = trace_term_sym + + &denoms[2 * row_idx + 1] * &(&base_row_sum_sym - &ood_row_sum); + } + let composition_parts_ood = evals(&proof.composition_poly_parts_ood_evaluation); let number_of_parts = lde_composition_poly_parts_evaluation.len(); + // A malformed proof can give the regular and symmetric openings a + // different composition-poly-parts count; reject rather than let the + // shared `z_pow`/bounds checks below silently apply the wrong count + // to one side. + if number_of_parts != lde_composition_poly_parts_evaluation_sym.len() { + return None; + } let z_pow = &challenges.z.pow(number_of_parts); - // A malformed proof can make evaluation_point == z^N, reject. - let denom_composition = (evaluation_point - z_pow).inv().ok()?; - let mut h_terms = FieldElement::zero(); - for (j, h_i_upsilon) in lde_composition_poly_parts_evaluation.iter().enumerate() { + // A malformed proof can make evaluation_point == z^N, reject. Hand-rolled + // 2-element batch inverse (one inversion, three muls, no allocation): + // for a, b with product p = a*b, inv(p) * b = inv(a) and inv(p) * a = inv(b). + let denom_composition_raw = evaluation_point - z_pow; + let denom_composition_sym_raw = evaluation_point_sym - z_pow; + let denom_composition_prod_inv = + (&denom_composition_raw * &denom_composition_sym_raw).inv().ok()?; + let denom_composition = &denom_composition_prod_inv * &denom_composition_sym_raw; + let denom_composition_sym = &denom_composition_prod_inv * &denom_composition_raw; + + let mut h_sum_zpow = FieldElement::::zero(); + let mut h_sum = FieldElement::::zero(); + let mut h_sum_sym = FieldElement::::zero(); + for j in 0..number_of_parts { + let h_i_upsilon = &lde_composition_poly_parts_evaluation[j]; + let h_i_upsilon_sym = &lde_composition_poly_parts_evaluation_sym[j]; // Bounds-check via `.get(j)?`: a malformed opening may have more // parts than the proof header advertises. - let h_i_zpower = proof.composition_poly_parts_ood_evaluation.get(j)?; + let h_i_zpower = composition_parts_ood.get(j)?; let gamma = challenges.gammas.get(j)?; - let h_i_term = (h_i_upsilon - h_i_zpower) * gamma; - h_terms += h_i_term; + h_sum_zpow = h_sum_zpow + h_i_zpower * gamma; + h_sum = h_sum + h_i_upsilon * gamma; + h_sum_sym = h_sum_sym + h_i_upsilon_sym * gamma; } - h_terms *= denom_composition; + let h_terms = (&h_sum - &h_sum_zpow) * denom_composition; + let h_terms_sym = (&h_sum_sym - &h_sum_zpow) * denom_composition_sym; - Some(trace_term + h_terms) + Some((trace_term + h_terms, trace_term_sym + h_terms_sym)) } /// Verifies one or more STARK proofs with their corresponding AIRs. @@ -703,15 +836,56 @@ pub trait IsStarkVerifier< transcript: &mut (impl IsStarkTranscript + Clone), expected_bus_balance: &FieldElement, ) -> bool + where + FieldElement: AsBytes + Sync + Send, + FieldElement: AsBytes + Sync + Send, + MultiProof: for<'a> rkyv::Serialize< + rkyv::api::high::HighSerializer< + rkyv::util::AlignedVec, + rkyv::ser::allocator::ArenaHandle<'a>, + rkyv::rancor::Error, + >, + >, + { + let bytes = match rkyv::to_bytes::(multi_proof) { + Ok(bytes) => bytes, + Err(e) => { + error!("failed to archive proof for verification: {e}"); + return false; + } + }; + // SAFETY: `bytes` was produced by `rkyv::to_bytes` on the line above, + // so it is a valid, aligned archive of this exact type. + let archived = unsafe { + rkyv::access_unchecked::>(&bytes) + }; + Self::multi_verify_archived( + airs, + archived.proofs.as_slice(), + transcript, + expected_bus_balance, + ) + } + + /// Verifies one or more rkyv-archived STARK proofs read **in place** from + /// their archive buffer — no proof deserialization, no per-field allocation. + /// This is the single verification implementation; [`Self::multi_verify`] + /// is a thin shim that archives an owned [`MultiProof`] and delegates here. + fn multi_verify_archived( + airs: &[&dyn AIR], + proofs: &[ArchivedStarkProof], + transcript: &mut (impl IsStarkTranscript + Clone), + expected_bus_balance: &FieldElement, + ) -> bool where FieldElement: AsBytes + Sync + Send, FieldElement: AsBytes + Sync + Send, { - if airs.len() != multi_proof.proofs.len() { + if airs.len() != proofs.len() { error!( "AIR count ({}) does not match proof count ({})", airs.len(), - multi_proof.proofs.len() + proofs.len() ); return false; } @@ -725,15 +899,24 @@ pub trait IsStarkVerifier< // For preprocessed tables, use the hardcoded commitment (verifier cannot // trust the prover). For normal tables, use the commitment from the proof. - for (idx, (air, proof)) in airs.iter().zip(&multi_proof.proofs).enumerate() { + for (idx, (air, proof)) in airs.iter().zip(proofs).enumerate() { // Soundness: the number of composition-poly parts is fixed by the AIR's // degree bound, NOT chosen by the prover. Deriving it from the proof would // let a malicious prover inflate the part count, widening the composition // polynomial's degree space and weakening the low-degree test. Reject any // proof whose advertised part count disagrees with the AIR. - if proof.trace_length == 0 + let trace_length = proof.trace_length.to_native() as usize; + if trace_length == 0 || proof.composition_poly_parts_ood_evaluation.len() - != air.composition_poly_degree_bound(proof.trace_length) / proof.trace_length + != air.composition_poly_degree_bound(trace_length) / trace_length + { + return false; + } + // The archive is read in place without validation; reject an OOD + // table whose advertised dimensions disagree with its data length + // (or that has no rows) before any row access indexes into it. + if !proof.trace_ood_evaluations.dimensions_consistent() + || proof.trace_ood_evaluations.height() == 0 { return false; } @@ -741,7 +924,7 @@ pub trait IsStarkVerifier< // Preprocessed table: VERIFY precomputed commitment matches hardcoded. // This is the critical soundness check - ensures prover used correct precomputed values. let expected_precomputed = air.precomputed_commitment(); - match &proof.lde_trace_precomputed_merkle_root { + match proof.lde_trace_precomputed_merkle_root.as_ref() { Some(actual) if *actual == expected_precomputed => { // OK - commitment matches hardcoded } @@ -790,7 +973,7 @@ pub trait IsStarkVerifier< // boundary constraints on LogUp columns, so the bus balance check is // the only cross-table validation. - for (idx, (air, proof)) in airs.iter().zip(&multi_proof.proofs).enumerate() { + for (idx, (air, proof)) in airs.iter().zip(proofs).enumerate() { if air.has_trace_interaction() && proof.bus_public_inputs.is_none() { error!( "Table {idx}: AIR has LogUp interactions but proof is missing bus_public_inputs" @@ -812,7 +995,7 @@ pub trait IsStarkVerifier< // state after Phase B, domain-separated by table index). This matches // the prover's forking and makes per-table verification independent. - for (idx, (air, proof)) in airs.iter().zip(&multi_proof.proofs).enumerate() { + for (idx, (air, proof)) in airs.iter().zip(proofs).enumerate() { // Must match prover: fork with domain separator for multi-table, // use original transcript directly for single-table. let num_tables = airs.len(); @@ -822,19 +1005,28 @@ pub trait IsStarkVerifier< } // Phase C: replay aux commitment - if let Some(root) = proof.lde_trace_aux_merkle_root { - table_transcript.append_bytes(&root); + if let Some(root) = proof.lde_trace_aux_merkle_root.as_ref() { + table_transcript.append_bytes(root); } // Bind table_contribution (L) to transcript, matching prover. - if let Some(ref bpi) = proof.bus_public_inputs { - table_transcript.append_field_element(&bpi.table_contribution); + if let Some(bpi) = proof.bus_public_inputs.as_ref() { + table_transcript.append_field_element(bpi.table_contribution.as_native()); } + // The AIR API takes owned public inputs; materialize the (tiny) PI. + // For the VM verifier `PI = ()` and this is a no-op. + let public_inputs: PI = + match rkyv::deserialize::(&proof.public_inputs) { + Ok(pi) => pi, + Err(_) => return false, + }; + // Rounds 2-4: verify if !Self::verify_rounds_2_to_4( *air, proof, + &public_inputs, &mut table_transcript, lookup_challenges.clone(), ) { @@ -860,11 +1052,11 @@ pub trait IsStarkVerifier< if needs_lookup_challenges { let mut total = FieldElement::::zero(); - for (air, proof) in airs.iter().zip(&multi_proof.proofs) { + for (air, proof) in airs.iter().zip(proofs) { if air.has_trace_interaction() - && let Some(interaction) = &proof.bus_public_inputs + && let Some(interaction) = proof.bus_public_inputs.as_ref() { - total = total + &interaction.table_contribution; + total = total + interaction.table_contribution.as_native(); } } @@ -894,6 +1086,13 @@ pub trait IsStarkVerifier< FieldElement: AsBytes + Sync + Send, FieldElement: AsBytes + Sync + Send, PI: Clone, + MultiProof: for<'a> rkyv::Serialize< + rkyv::api::high::HighSerializer< + rkyv::util::AlignedVec, + rkyv::ser::allocator::ArenaHandle<'a>, + rkyv::rancor::Error, + >, + >, { let multi_proof = MultiProof { proofs: vec![proof.clone()], @@ -905,7 +1104,8 @@ pub trait IsStarkVerifier< /// already been replayed and the RAP challenges are known. fn replay_rounds_after_round_1( air: &dyn AIR, - proof: &StarkProof, + proof: &ArchivedStarkProof, + public_inputs: &PI, domain: &VerifierDomain, transcript: &mut impl IsStarkTranscript, rap_challenges: Vec>, @@ -914,18 +1114,25 @@ pub trait IsStarkVerifier< FieldElement: AsBytes, FieldElement: AsBytes, { + crate::profile_markers::step_marker::< + { crate::profile_markers::STEP_REPLAY_ROUNDS_AFTER_ROUND_1 }, + >(); // =================================== // ==========| Round 2 |========== // =================================== // <<<< Receive challenge: 𝛽 let beta = transcript.sample_field_element(); - let trace_length = proof.trace_length; + let trace_length = proof.trace_length.to_native() as usize; + let bus_public_inputs = proof + .bus_public_inputs + .as_ref() + .map(|bpi| BusPublicInputs::from_contribution(bpi.table_contribution.as_native().clone())); let num_boundary_constraints = air .boundary_constraints( - &proof.public_inputs, + public_inputs, &rap_challenges, - proof.bus_public_inputs.as_ref(), + bus_public_inputs.as_ref(), trace_length, ) .constraints @@ -954,14 +1161,16 @@ pub trait IsStarkVerifier< ); // <<<< Receive values: tⱼ(zgᵏ) - let trace_ood_evaluations_columns = proof.trace_ood_evaluations.columns(); - for col in trace_ood_evaluations_columns.iter() { - for elem in col.iter() { - transcript.append_field_element(elem); + // Column-major append (matches `Table::columns()` order) reading the + // archived rows in place, without materializing transposed columns. + let ood = &proof.trace_ood_evaluations; + for col_idx in 0..ood.width() { + for row_idx in 0..ood.height() { + transcript.append_field_element(&ood.get_row(row_idx)[col_idx]); } } // <<<< Receive value: Hᵢ(z^N) - for element in proof.composition_poly_parts_ood_evaluation.iter() { + for element in evals(&proof.composition_poly_parts_ood_evaluation).iter() { transcript.append_field_element(element); } @@ -970,6 +1179,7 @@ pub trait IsStarkVerifier< // =================================== let num_terms_composition_poly = proof.composition_poly_parts_ood_evaluation.len(); + let num_terms_trace = air.context().transition_offsets.len() * air.step_size() * air.context().trace_columns; let gamma = transcript.sample_field_element(); @@ -991,7 +1201,7 @@ pub trait IsStarkVerifier< let gammas = deep_composition_coefficients; // FRI commit phase - let merkle_roots = &proof.fri_layers_merkle_roots; + let merkle_roots = proof.fri_layers_merkle_roots.as_slice(); let mut zetas = merkle_roots .iter() .map(|root| { @@ -1007,16 +1217,16 @@ pub trait IsStarkVerifier< zetas.push(transcript.sample_field_element()); // <<<< Receive value: pₙ - transcript.append_field_element(&proof.fri_last_value); + transcript.append_field_element(proof.fri_last_value.as_native()); // Receive grinding value let security_bits = air.context().proof_options.grinding_factor; let mut grinding_seed = [0u8; 32]; if security_bits > 0 - && let Some(nonce_value) = proof.nonce + && let Some(nonce_value) = proof.nonce.as_ref() { grinding_seed = transcript.state(); - transcript.append_bytes(&nonce_value.to_be_bytes()); + transcript.append_bytes(&nonce_value.to_native().to_be_bytes()); } // FRI query phase @@ -1040,7 +1250,8 @@ pub trait IsStarkVerifier< /// Verifies a single table after round 1 has been replayed. fn verify_rounds_2_to_4( air: &dyn AIR, - proof: &StarkProof, + proof: &ArchivedStarkProof, + public_inputs: &PI, transcript: &mut impl IsStarkTranscript, rap_challenges: Vec>, ) -> bool @@ -1048,7 +1259,7 @@ pub trait IsStarkVerifier< FieldElement: AsBytes + Sync + Send, FieldElement: AsBytes + Sync + Send, { - let domain = new_verifier_domain(air, proof.trace_length); + let domain = new_verifier_domain(air, proof.trace_length.to_native() as usize); // Verify there are enough queries if proof.query_list.len() < air.options().fri_number_of_queries { @@ -1060,14 +1271,24 @@ pub trait IsStarkVerifier< #[cfg(feature = "instruments")] let timer1 = Instant::now(); - let challenges = - Self::replay_rounds_after_round_1(air, proof, &domain, transcript, rap_challenges); + let challenges = Self::replay_rounds_after_round_1( + air, + proof, + public_inputs, + &domain, + transcript, + rap_challenges, + ); // verify grinding let security_bits = air.context().proof_options.grinding_factor; if security_bits > 0 { - let nonce_is_valid = proof.nonce.is_some_and(|nonce_value| { - grinding::is_valid_nonce(&challenges.grinding_seed, nonce_value, security_bits) + let nonce_is_valid = proof.nonce.as_ref().is_some_and(|nonce_value| { + grinding::is_valid_nonce( + &challenges.grinding_seed, + nonce_value.to_native(), + security_bits, + ) }); if !nonce_is_valid { @@ -1087,7 +1308,13 @@ pub trait IsStarkVerifier< #[cfg(feature = "instruments")] let timer2 = Instant::now(); - if !Self::step_2_verify_claimed_composition_polynomial(air, proof, &domain, &challenges) { + if !Self::step_2_verify_claimed_composition_polynomial( + air, + proof, + public_inputs, + &domain, + &challenges, + ) { #[cfg(not(feature = "test_fiat_shamir"))] error!("Composition Polynomial verification failed"); return false; diff --git a/docs/continuations_design.md b/docs/continuations_design.md index 9c3f54747..5ff60e1d6 100644 --- a/docs/continuations_design.md +++ b/docs/continuations_design.md @@ -499,7 +499,7 @@ fresh agents) of the register/x254 chain, the L2G root binding, and completeness-by-enumeration found no false-accept: each forgery is caught by a Merkle/hash collision, a bus imbalance, or a Fiat-Shamir divergence. -The bundle derives serde and round-trips through `bincode` (exactly like a +The bundle derives rkyv and round-trips through the same archive format (exactly like a monolithic `VmProof`); the CLI drives it via `prove --continuations` (writes the bundle) and `verify --continuations` (checks bundle + ELF only). `prove` picks the epoch size from `--epoch-size-log2 N` (`N=20` means 1,048,576 cycles), defaulting @@ -521,7 +521,7 @@ recursion/aggregation layer (deferred). ordering check (§3.3), the `MU` selector (§3.4), the **power-of-two epoch size** (§3.5), **cross-epoch registers** (§6), the **commit index x254** across epochs (§6), the **Fiat-Shamir statement binding** (§7), and the **standalone split - prover/verifier** (§8) — bundle serialized with `bincode` and driven from the CLI + prover/verifier** (§8) — bundle serialized with rkyv and driven from the CLI (`prove`/`verify --continuations`). - **The committed code implements Design X** (`MU` gates every L2G interaction), which is the sound design. Design Y was implemented briefly, then found unsound diff --git a/executor/Cargo.toml b/executor/Cargo.toml index 5d1e4ae49..3f278e1c6 100644 --- a/executor/Cargo.toml +++ b/executor/Cargo.toml @@ -13,10 +13,3 @@ ecsm = { path = "../crypto/ecsm" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tiny-keccak = { version = "2.0", features = ["keccak"] } -# Exact pin: must match the fixture writer + guest so the rkyv ProgramInput -# layout the executor tests read stays consistent (see tooling/ethrex-fixtures). -rkyv = { version = "=0.8.16", features = ["std", "unaligned"] } -# Native reference for the ethrex tests (host-side `execution_program` with -# `NativeCrypto`). Pinned to the same ethrex rev as the guest ELF -# (executor/programs/rust/ethrex) — the open LambdaVM-backend PR branch. -ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "156cb8d6a3974f411d71622eecd1b249ee37ff1c", package = "ethrex-guest-program" } diff --git a/executor/programs/asm/test_private_input_xpage.s b/executor/programs/asm/test_private_input_xpage.s index abe58452c..a78bfdd1c 100644 --- a/executor/programs/asm/test_private_input_xpage.s +++ b/executor/programs/asm/test_private_input_xpage.s @@ -2,7 +2,7 @@ .globl main main: # Read private input directly from 0xFF000000 (memory-mapped). - # Layout: [len:u32 LE] [data...] + # Layout: [len:u32 LE] [12 reserved bytes] [data at +16] # Commits 8 bytes of data. # # Note: lui in RV64 sign-extends to 64 bits. lui with 0xFF000 would give @@ -16,11 +16,11 @@ main: # Read length at 0xFF000000 lw t3, 0(t0) # 2: t3 = length - # Load 8 bytes of data at 0xFF000008 (aligned, 4 bytes into data region) - ld t1, 8(t0) # 3 + # Load 8 bytes of data at 0xFF000010 (aligned, start of data region) + ld t1, 16(t0) # 3 - # Commit 8 bytes from 0xFF000008 - addi a1, t0, 8 # 4: buf_addr = 0xFF000008 + # Commit 8 bytes from 0xFF000010 + addi a1, t0, 16 # 4: buf_addr = 0xFF000010 li a0, 1 # 5: fd = 1 li a2, 8 # 6: count = 8 li a7, 64 # 7: syscall = Commit diff --git a/executor/programs/rust/ef_io_demo/Cargo.lock b/executor/programs/rust/ef_io_demo/Cargo.lock new file mode 100644 index 000000000..c2993d8c6 --- /dev/null +++ b/executor/programs/rust/ef_io_demo/Cargo.lock @@ -0,0 +1,331 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "const-default" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b396d1f76d455557e1218ec8066ae14bba60b4b36ecd55577ba979f5db7ecaa" + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "ef_io_demo" +version = "0.1.0" +dependencies = [ + "lambda-vm-syscalls", +] + +[[package]] +name = "embedded-alloc" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f2de9133f68db0d4627ad69db767726c99ff8585272716708227008d3f1bddd" +dependencies = [ + "const-default", + "critical-section", + "linked_list_allocator", + "rlsf", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "lambda-vm-syscalls" +version = "0.1.0" +dependencies = [ + "embedded-alloc", + "getrandom 0.2.17", + "getrandom 0.3.4", + "lazy_static", + "rand", + "riscv", + "thiserror", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "linked_list_allocator" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b23ac50abb8261cb38c6e2a7192d3302e0836dac1628f6a93b82b4fad185897" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "riscv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05cfa3f7b30c84536a9025150d44d26b8e1cc20ddf436448d74cd9591eefb25" +dependencies = [ + "critical-section", + "embedded-hal", + "paste", + "riscv-macros", + "riscv-pac", +] + +[[package]] +name = "riscv-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d323d13972c1b104aa036bc692cd08b822c8bbf23d79a27c526095856499799" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "riscv-pac" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8188909339ccc0c68cfb5a04648313f09621e8b87dc03095454f1a11f6c5d436" + +[[package]] +name = "rlsf" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1646a59a9734b8b7a0ac51689388a60fe1625d4b956348e9de07591a1478457a" +dependencies = [ + "cfg-if", + "const-default", + "libc", + "rustversion", + "svgbobdoc", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "svgbobdoc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2c04b93fc15d79b39c63218f15e3fdffaa4c227830686e3b7c5f41244eb3e50" +dependencies = [ + "base64", + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-width", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "zerocopy" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] diff --git a/executor/src/flamegraph.rs b/executor/src/flamegraph.rs index f9b447d19..4764d71a2 100644 --- a/executor/src/flamegraph.rs +++ b/executor/src/flamegraph.rs @@ -154,7 +154,7 @@ impl FlamegraphGenerator { /// Demangle a Rust symbol name using the official rustc-demangle crate. /// /// Uses the alternate format (`{:#}`) to omit the hash suffix for cleaner output. -pub(crate) fn demangle(name: &str) -> String { +pub fn demangle(name: &str) -> String { // Use rustc-demangle with alternate format to omit hash format!("{:#}", rustc_demangle(name)) } diff --git a/executor/src/vm/execution.rs b/executor/src/vm/execution.rs index 99eb0a00f..a1a766127 100644 --- a/executor/src/vm/execution.rs +++ b/executor/src/vm/execution.rs @@ -302,6 +302,23 @@ impl InstructionCache { } } +/// Decode a `stark::profile_markers::step_marker` hit at `pc`: the marker +/// convention is `addi x0, x0, N` (an `ArithImm` with `dst == 0`, `src == 0`, +/// `op == Add`, `N != 0`), which real code never emits spontaneously since +/// writes to `x0` are always discarded and the canonical NOP is `addi x0, x0, +/// 0`. Returns the marker's `N` if `pc` decodes to one. +pub fn decode_step_marker(instructions: &InstructionCache, pc: u64) -> Option { + match instructions.get(pc)? { + Instruction::ArithImm { + dst: 0, + src: 0, + op: crate::vm::instruction::decoding::ArithOp::Add, + imm, + } if *imm != 0 => Some(*imm as u32), + _ => None, + } +} + #[derive(thiserror::Error, Debug)] pub enum ExecutorError { #[error("Failed to decode instruction: {0}")] diff --git a/executor/src/vm/memory.rs b/executor/src/vm/memory.rs index f349eeae6..8ad0a9fb6 100644 --- a/executor/src/vm/memory.rs +++ b/executor/src/vm/memory.rs @@ -48,10 +48,25 @@ pub const MAX_PUBLIC_OUTPUT_TOTAL_SIZE: u64 = 1024 * 1024; pub const MAX_PRIVATE_INPUT_SIZE: u64 = 64 * 1024 * 1024; /// Fixed high address where private input is mapped. Guest programs can read /// directly from this address (ZisK-style memory-mapped input). -/// Layout: 4-byte LE length prefix at `PRIVATE_INPUT_START_INDEX`, then data at +4. +/// Layout: a 16-byte header — 4-byte LE length prefix at +/// `PRIVATE_INPUT_START_INDEX`, then 12 reserved bytes — followed by the +/// payload at `+PRIVATE_INPUT_PAYLOAD_OFFSET`. The payload base is 16-aligned +/// so guests can read structured (e.g. rkyv-archived) input in place with +/// naturally-aligned loads. /// Must match `PRIVATE_INPUT_START` in `syscalls/src/syscalls.rs`. pub const PRIVATE_INPUT_START_INDEX: u64 = 0xFF000000; +/// Byte offset of the private-input payload from [`PRIVATE_INPUT_START_INDEX`]: +/// the size of the `[len: u32 LE][reserved: 12 bytes]` header. A multiple of 16 +/// so the payload base stays 16-aligned. +/// Must match `PRIVATE_INPUT_PAYLOAD_OFFSET` in `syscalls/src/syscalls.rs`. +pub const PRIVATE_INPUT_PAYLOAD_OFFSET: u64 = 16; + +const _: () = assert!( + (PRIVATE_INPUT_START_INDEX + PRIVATE_INPUT_PAYLOAD_OFFSET).is_multiple_of(16), + "private-input payload base must be 16-aligned", +); + #[derive(Default, Debug, Clone)] pub struct Memory { cells: U64HashMap<[u8; 4]>, @@ -218,8 +233,9 @@ impl Memory { Ok(self.public_output.clone()) } - /// Pre-loads private input bytes at `PRIVATE_INPUT_START_INDEX` as a - /// 4-byte LE length prefix followed by the raw data. The guest reads these + /// Pre-loads private input bytes at `PRIVATE_INPUT_START_INDEX`: a 4-byte + /// LE length prefix, 12 reserved (zero) bytes, then the raw data at + /// `+PRIVATE_INPUT_PAYLOAD_OFFSET` (16-aligned). The guest reads these /// bytes directly via normal RISC-V loads (ZisK-style memory-mapped input). pub fn store_private_inputs(&mut self, inputs: Vec) -> Result<(), MemoryError> { if inputs.is_empty() { @@ -231,7 +247,10 @@ impl Memory { let len_u32 = u32::try_from(inputs.len()).map_err(|_| MemoryError::PrivateInputSizeExceeded)?; self.store_word(PRIVATE_INPUT_START_INDEX, len_u32)?; - self.set_bytes_aligned(PRIVATE_INPUT_START_INDEX + 4, &inputs)?; + self.set_bytes_aligned( + PRIVATE_INPUT_START_INDEX + PRIVATE_INPUT_PAYLOAD_OFFSET, + &inputs, + )?; Ok(()) } diff --git a/executor/tests/README.md b/executor/tests/README.md index eddd3b525..39fd13813 100644 --- a/executor/tests/README.md +++ b/executor/tests/README.md @@ -5,6 +5,10 @@ The `ethrex_*.bin` files are rkyv-serialized `ethrex_guest_program::l1::ProgramInput` values consumed by the ethrex guest (`executor/programs/rust/ethrex`). +The native-reference tests live in `tooling/ethrex-tests` (a detached +workspace: ethrex pins rkyv `unaligned`, which must not feature-unify with the +main workspace's aligned proof format). + The ethrex guest, the native test reference, and the fixture generator are all pinned to the same ethrex revision (the open LambdaVM-backend PR branch, until it merges to `main`): diff --git a/executor/tests/asm.rs b/executor/tests/asm.rs index a1c9baf2b..11a2fc644 100644 --- a/executor/tests/asm.rs +++ b/executor/tests/asm.rs @@ -33,8 +33,8 @@ fn test_private_input_memory_mapped() { let input: Vec = (0u8..16).collect(); let executor = Executor::new(&program, input.clone()).unwrap(); let result = executor.run().unwrap(); - // Committed bytes are at 0xFF000008 = data bytes [4..12] - assert_eq!(result.return_values.memory_values, input[4..12].to_vec()); + // Committed bytes are at 0xFF000010 = data bytes [0..8] + assert_eq!(result.return_values.memory_values, input[0..8].to_vec()); } #[test] diff --git a/executor/tests/rust.rs b/executor/tests/rust.rs index 458a0bd6c..7dcb88e29 100644 --- a/executor/tests/rust.rs +++ b/executor/tests/rust.rs @@ -267,72 +267,6 @@ fn test_args_panics() { } } -/// Larger-block smoke test: a synthetic ethrex block with 10 ETH transfers. -/// (Replaces the old `ethrex_hoodi.bin` real-block fixture, which was in the -/// pre-Crypto-trait ethrex format and no longer deserializes.) Fixture is -/// generated by `tooling/ethrex-fixtures`; see `tests/README.md`. -#[ignore = "heavier synthetic block (10 txs); run in the dedicated --ignored CI step"] -#[test] -fn test_ethrex() { - use ethrex_guest_program::crypto::NativeCrypto; - use ethrex_guest_program::l1::{ProgramInput, execution_program}; - use rkyv::rancor::Error; - use std::fs; - use std::sync::Arc; - let inputs = fs::read("tests/ethrex_10_transfers.bin").unwrap(); - let input = rkyv::from_bytes::(&inputs).unwrap(); - let output = execution_program(input, Arc::new(NativeCrypto)).unwrap(); - run_program_and_check_public_output( - "./program_artifacts/rust/ethrex.elf", - output.encode(), - inputs, - ); -} - -/// Executes a stateless ethrex block containing a single (plain ETH transfer) -/// transaction. Execution only — no proving — against the ethrex guest ELF -/// built from the same pinned ethrex revision as the native reference. The -/// fixture is a serialized `ProgramInput`; see `tests/README.md` for provenance. -/// -/// The fixture is generated by `tooling/ethrex-fixtures` at the same ethrex rev -/// as the guest (see `tests/README.md`). -#[test] -fn test_ethrex_simple_tx() { - use ethrex_guest_program::crypto::NativeCrypto; - use ethrex_guest_program::l1::{ProgramInput, execution_program}; - use rkyv::rancor::Error; - use std::sync::Arc; - let inputs = std::fs::read("tests/ethrex_simple_tx.bin").unwrap(); - let input = rkyv::from_bytes::(&inputs).unwrap(); - let output = execution_program(input, Arc::new(NativeCrypto)).unwrap(); - run_program_and_check_public_output( - "./program_artifacts/rust/ethrex.elf", - output.encode(), - inputs, - ); -} - -/// Executes a stateless ethrex block with NO transactions (empty block). -/// Execution only — no proving. Pins the committed `ethrex_empty_block.bin` -/// fixture into the default suite so its rkyv `ProgramInput` layout (the 0-tx -/// edge case) is exercised and stays consistent with the guest across ethrex -/// rev bumps. Mirrors `test_ethrex_simple_tx`; see `tests/README.md`. -#[test] -fn test_ethrex_empty_block() { - use ethrex_guest_program::crypto::NativeCrypto; - use ethrex_guest_program::l1::{ProgramInput, execution_program}; - use rkyv::rancor::Error; - use std::sync::Arc; - let inputs = std::fs::read("tests/ethrex_empty_block.bin").unwrap(); - let input = rkyv::from_bytes::(&inputs).unwrap(); - let output = execution_program(input, Arc::new(NativeCrypto)).unwrap(); - run_program_and_check_public_output( - "./program_artifacts/rust/ethrex.elf", - output.encode(), - inputs, - ); -} - #[ignore = "Ignored until the vm is fast enough to run this test"] #[test] fn test_ckzg() { diff --git a/prover/Cargo.toml b/prover/Cargo.toml index ff6922f63..18f2b12d4 100644 --- a/prover/Cargo.toml +++ b/prover/Cargo.toml @@ -11,6 +11,7 @@ cuda = ["stark/cuda"] test-cuda-faults = ["cuda", "stark/test-cuda-faults"] debug-checks = ["stark/debug-checks"] instruments = ["stark/instruments"] +profile-markers = ["stark/profile-markers"] disk-spill = ["stark/disk-spill"] [dependencies] @@ -19,17 +20,15 @@ crypto = { path = "../crypto/crypto" } math = { path = "../crypto/math" } executor = { path = "../executor" } ecsm = { path = "../crypto/ecsm" } -serde = { version = "1.0", features = ["derive"] } rayon = { version = "1.8.0", optional = true } sysinfo = { version = "0.31", default-features = false, features = ["system"] } log = "0.4" sha3 = { version = "0.10.8", default-features = false } +rkyv = { version = "0.8.10", default-features = false, features = ["alloc", "bytecheck", "aligned"] } [dev-dependencies] env_logger = "*" criterion = { version = "0.5", default-features = false } -bincode = "1" -postcard = { version = "1.0", features = ["alloc"] } tikv-jemallocator = "0.6" tikv-jemalloc-ctl = { version = "0.6", features = ["stats"] } tiny-keccak = { version = "2.0", features = ["keccak"] } diff --git a/prover/src/continuation.rs b/prover/src/continuation.rs index ccdd5a6f9..ea9f0c948 100644 --- a/prover/src/continuation.rs +++ b/prover/src/continuation.rs @@ -247,7 +247,7 @@ struct EpochStart<'a> { /// Note: continuation epochs use the L2G memory bookend, so PAGE is skipped and the /// per-epoch page config set is empty — the verifier builds the AIRs with no PAGE /// tables rather than trusting any prover-supplied page config. -#[derive(serde::Serialize, serde::Deserialize)] +#[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] struct EpochProof { /// The epoch's STARK proof (its tables + the epoch-local L2G sub-table last). proof: MultiProof, @@ -278,8 +278,8 @@ struct EpochProof { /// rebuild the genesis image — bound by the global proof's genesis-from-ELF check). /// /// `verify_continuation` checks this using only the bundle and the ELF. It derives -/// serde, so it round-trips through `bincode` exactly like a monolithic `VmProof`. -#[derive(serde::Serialize, serde::Deserialize)] +/// rkyv, so it round-trips through the same archive format as a monolithic `VmProof`. +#[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct ContinuationProof { epochs: Vec, global: MultiProof, @@ -496,9 +496,19 @@ fn verify_epoch( .copied() .unwrap_or(0) as u64; + let proof_bytes = match rkyv::to_bytes::(&epoch.proof) { + Ok(bytes) => bytes, + Err(_) => return false, + }; + // SAFETY: `proof_bytes` was produced by `rkyv::to_bytes` on the line above. + let archived = unsafe { + rkyv::access_unchecked::>(&proof_bytes) + }; + let archived_proofs = archived.proofs.as_slice(); + let expected = match compute_expected_commit_bus_balance( &refs, - &epoch.proof, + archived_proofs, &epoch.public_output, commit_start_index, &mut seed(), @@ -507,7 +517,7 @@ fn verify_epoch( None => return false, }; - if !Verifier::multi_verify(&refs, &epoch.proof, &mut seed(), &expected) { + if !Verifier::multi_verify_archived(&refs, archived_proofs, &mut seed(), &expected) { return false; } @@ -1030,17 +1040,18 @@ mod tests { assert_eq!(out.as_deref(), Some(&[0xAA, 0xBB, 0xCC, 0xDD][..])); } - // A bundle survives a bincode round-trip and still verifies to the same output — + // A bundle survives an rkyv round-trip and still verifies to the same output — // the serialization path the CLI's `prove`/`verify --continuations` relies on. #[test] - fn test_continuation_bincode_roundtrip() { + fn test_continuation_rkyv_roundtrip() { let _ = env_logger::builder().is_test(true).try_init(); let elf_bytes = asm_elf_bytes("test_commit_split"); let bundle = prove_continuation(&elf_bytes, &[], 4, &ProofOptions::default_test_options()).unwrap(); - let bytes = bincode::serialize(&bundle).unwrap(); - let restored: ContinuationProof = bincode::deserialize(&bytes).unwrap(); + let bytes = rkyv::to_bytes::(&bundle).unwrap(); + let restored: ContinuationProof = + rkyv::from_bytes::<_, rkyv::rancor::Error>(&bytes).unwrap(); let out = verify_continuation(&elf_bytes, &restored, &ProofOptions::default_test_options()) .unwrap(); diff --git a/prover/src/lib.rs b/prover/src/lib.rs index 6bbde8b84..cbee1f51a 100644 --- a/prover/src/lib.rs +++ b/prover/src/lib.rs @@ -19,11 +19,15 @@ mod debug_report; #[cfg(feature = "instruments")] pub mod instruments; mod paged_mem; +pub use stark::profile_markers; mod statement; pub mod tables; pub mod test_utils; #[cfg(test)] pub mod tests; +pub mod vkey; + +pub use vkey::VmVerifyingKey; use std::fmt; @@ -61,13 +65,13 @@ use crate::test_utils::{ // Re-exported so downstream verifier guests (e.g. the in-VM recursion guest) can // name the proof-options type carried in their private input alongside `VmProof`. pub use stark::proof::options::{GoldilocksCubicProofOptions, ProofOptions}; -use stark::proof::stark::MultiProof; +use stark::proof::stark::{ArchivedMultiProof, ArchivedStarkProof, MultiProof}; /// A run-length encoded range of contiguous zero-initialized 4KB pages. /// /// Represents `count` contiguous pages starting at `base`, used for /// runtime-allocated memory (stack, heap) not covered by ELF segments. -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct RuntimePageRange { /// Base address of the first page (4KB-aligned). pub base: u64, @@ -82,7 +86,7 @@ pub const FIXED_TABLE_COUNT: usize = 11; /// Number of chunks for each split table. /// The verifier needs this to reconstruct matching AIRs. -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct TableCounts { pub cpu: usize, pub lt: usize, @@ -154,7 +158,7 @@ impl TableCounts { /// A complete VM proof bundle containing the STARK proof and metadata /// needed by the verifier to reconstruct the AIR configuration. -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct VmProof { /// The multi-table STARK proof. pub proof: MultiProof, @@ -171,6 +175,103 @@ pub struct VmProof { /// These pages are NOT preprocessed — the verifier reconstructs them /// as non-preprocessed tables starting at `PRIVATE_INPUT_START_INDEX`. pub num_private_input_pages: usize, + /// Digest of the [`VmVerifyingKey`] the proof was made against. Bound + /// into the Fiat-Shamir statement and checked by the verifier against + /// its own vkey's digest before any STARK work. + pub vk_digest: [u8; 32], +} + +/// The private-input bundle the recursion verifier guest consumes: an inner +/// proof plus everything needed to verify it (inner ELF, the inner prover's +/// options, and the host-derived verifying key). +/// +/// Archived as one rkyv blob so the guest reads every field straight from the +/// input buffer with no deserialization pass. +#[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +pub struct RecursionInput { + pub vm_proof: VmProof, + pub inner_elf: Vec, + pub options: ProofOptions, + pub vkey: VmVerifyingKey, +} + +// ============================================================================ +// Recursion-input wire format: magic/version prefix + rkyv archive +// ============================================================================ +// +// The guest reads the archive in place with naturally-aligned loads, so the +// archive's first byte must sit 16-aligned in guest memory. The executor maps +// the private-input payload at a 16-aligned base +// (`PRIVATE_INPUT_START + PRIVATE_INPUT_PAYLOAD_OFFSET`), so the prefix is +// pure framing — a magic + version tag the guest validates before the unsafe +// access — sized to a multiple of the alignment so the archive after it stays +// aligned. Asserted below against the executor ABI. + +/// 4-byte magic identifying a lambda-vm recursion input blob ("LVMR"). +pub const RECURSION_INPUT_MAGIC: [u8; 4] = *b"LVMR"; + +/// Wire-format version of the recursion input blob. +pub const RECURSION_INPUT_VERSION: u32 = 1; + +/// Required alignment (bytes) of the archive's first byte in guest memory. +pub const RECURSION_INPUT_ALIGN: usize = 16; + +/// Prefix length: `magic(4) + version(4) + reserved(8) = 16` bytes — a +/// multiple of [`RECURSION_INPUT_ALIGN`] so the archive after it stays +/// aligned. Asserted below. +pub const RECURSION_INPUT_PREFIX_LEN: usize = 16; + +const _: () = { + let payload_base = (executor::vm::memory::PRIVATE_INPUT_START_INDEX + + executor::vm::memory::PRIVATE_INPUT_PAYLOAD_OFFSET) as usize; + assert!( + (payload_base + RECURSION_INPUT_PREFIX_LEN).is_multiple_of(RECURSION_INPUT_ALIGN), + "archive must start at a RECURSION_INPUT_ALIGN-aligned guest address", + ); + assert!( + RECURSION_INPUT_PREFIX_LEN >= 8, + "prefix must hold at least magic + version", + ); +}; + +/// Encode a [`RecursionInput`] into the on-wire blob: the 16-byte +/// `magic + version + reserved` prefix followed by the rkyv archive, +/// serialized directly after the prefix (no archive copy). The returned +/// buffer is 16-aligned and the prefix length is a multiple of the alignment, +/// so the archive is aligned both here and — because the guest payload base +/// is 16-aligned — in guest memory. +pub fn encode_recursion_input( + input: &RecursionInput, +) -> Result, Error> { + let mut blob = rkyv::util::AlignedVec::<{ RECURSION_INPUT_ALIGN }>::new(); + blob.extend_from_slice(&RECURSION_INPUT_MAGIC); + blob.extend_from_slice(&RECURSION_INPUT_VERSION.to_le_bytes()); + blob.extend_from_slice(&[0u8; 8]); // reserved + debug_assert_eq!(blob.len(), RECURSION_INPUT_PREFIX_LEN); + // rkyv computes alignment padding against positions from the buffer + // start; the prefix is a multiple of RECURSION_INPUT_ALIGN, so archive + // offsets keep the same residues and `&blob[PREFIX_LEN..]` is a valid + // aligned archive. + let blob = rkyv::api::high::to_bytes_in::<_, rkyv::rancor::Error>(input, blob) + .map_err(|e| Error::Execution(format!("rkyv encode failed: {e}")))?; + Ok(blob) +} + +/// Validate the wire prefix and return the archive bytes (zero-copy slice). +/// Returns `None` if the magic or version doesn't match — the caller should +/// halt cleanly rather than proceed into an `access_unchecked`. +pub fn recursion_archive_bytes(blob: &[u8]) -> Option<&[u8]> { + if blob.len() < RECURSION_INPUT_PREFIX_LEN { + return None; + } + if blob[0..4] != RECURSION_INPUT_MAGIC { + return None; + } + let version = u32::from_le_bytes([blob[4], blob[5], blob[6], blob[7]]); + if version != RECURSION_INPUT_VERSION { + return None; + } + Some(&blob[RECURSION_INPUT_PREFIX_LEN..]) } /// Error type for the prover crate. @@ -188,6 +289,8 @@ pub enum Error { Prover(String), /// Proof contains invalid table_counts (e.g. zero for a required table) InvalidTableCounts(String), + /// Supplied verifying key is malformed (wrong version or page count) + InvalidVerifyingKey(String), /// Continuation epoch size exponent is invalid. InvalidContinuationEpochSize(String), /// Continuation proof construction hit an internal invariant failure. @@ -208,6 +311,7 @@ impl fmt::Display for Error { Error::Execution(msg) => write!(f, "execution error: {msg}"), Error::Prover(msg) => write!(f, "proving error: {msg}"), Error::InvalidTableCounts(msg) => write!(f, "invalid table_counts: {msg}"), + Error::InvalidVerifyingKey(msg) => write!(f, "invalid verifying key: {msg}"), Error::InvalidContinuationEpochSize(msg) => { write!(f, "invalid continuation epoch size: {msg}") } @@ -452,6 +556,41 @@ impl VmAirs { register_init: Option<&[u32]>, page_commitments: Option<&[(u64, Commitment)]>, register_preprocessed: Option<(Commitment, usize)>, + ) -> Self { + Self::new_with_vkey( + elf, + proof_options, + minimal_bitwise, + page_configs, + table_counts, + decode_commitment, + include_halt, + register_init, + page_commitments, + register_preprocessed, + None, + ) + } + + /// Same as [`Self::new`] but accepts a precomputed [`VmVerifyingKey`]. + /// When `vkey` is `Some`, the bitwise, decode, register, keccak_rc and + /// per-page preprocessed commitments are taken from it instead of being + /// recomputed — recomputation (dominated by bitwise) is ~87% of verifier + /// cycles inside the recursion guest. Explicit `decode_commitment` / + /// `page_commitments` arguments still take precedence over the vkey. + #[allow(clippy::too_many_arguments)] + pub fn new_with_vkey( + elf: &Elf, + proof_options: &ProofOptions, + minimal_bitwise: bool, + page_configs: &[crate::tables::page::PageConfig], + table_counts: &TableCounts, + decode_commitment: Option, + include_halt: bool, + register_init: Option<&[u32]>, + page_commitments: Option<&[(u64, Commitment)]>, + register_preprocessed: Option<(Commitment, usize)>, + vkey: Option<&VmVerifyingKey>, ) -> Self { let cpus: Vec<_> = (0..table_counts.cpu) .map(|i| create_cpu_air(proof_options).with_name(&format!("CPU[{}]", i))) @@ -459,10 +598,12 @@ impl VmAirs { let bitwise = if minimal_bitwise { create_bitwise_air(proof_options) } else { - create_bitwise_air(proof_options).with_preprocessed( - bitwise::preprocessed_commitment(proof_options), - bitwise::NUM_PRECOMPUTED_COLS, - ) + let commitment = match vkey { + Some(vk) => vk.bitwise, + None => bitwise::preprocessed_commitment(proof_options), + }; + create_bitwise_air(proof_options) + .with_preprocessed(commitment, bitwise::NUM_PRECOMPUTED_COLS) }; let lts: Vec<_> = (0..table_counts.lt) .map(|i| create_lt_air(proof_options).with_name(&format!("LT[{}]", i))) @@ -479,10 +620,12 @@ impl VmAirs { let loads: Vec<_> = (0..table_counts.load) .map(|i| create_load_air(proof_options).with_name(&format!("LOAD[{}]", i))) .collect(); - let decode_root = decode_commitment.unwrap_or_else(|| { - decode::commitment_from_elf(elf, proof_options) - .expect("Failed to compute decode commitment") - }); + let decode_root = decode_commitment + .or_else(|| vkey.map(|vk| vk.decode)) + .unwrap_or_else(|| { + decode::commitment_from_elf(elf, proof_options) + .expect("Failed to compute decode commitment") + }); let decode = create_decode_air(proof_options) .with_preprocessed(decode_root, decode::NUM_PRECOMPUTED_COLS); let muls: Vec<_> = (0..table_counts.mul) @@ -498,8 +641,11 @@ impl VmAirs { let commit = create_commit_air(proof_options); let keccak = create_keccak_air(proof_options); let keccak_rnd = create_keccak_rnd_air(proof_options); + let keccak_rc_commitment = vkey + .map(|vk| vk.keccak_rc) + .unwrap_or_else(|| tables::keccak_rc::preprocessed_commitment(proof_options)); let keccak_rc = create_keccak_rc_air(proof_options).with_preprocessed( - tables::keccak_rc::preprocessed_commitment(proof_options), + keccak_rc_commitment, tables::keccak_rc::NUM_PRECOMPUTED_COLS, ); let ecsm = create_ecsm_air(proof_options); @@ -511,10 +657,11 @@ impl VmAirs { let register_init = register_init .map(<[u32]>::to_vec) .unwrap_or_else(|| register::register_init_from_entry_point(elf.entry_point)); - create_register_air(proof_options).with_preprocessed( - register::preprocessed_commitment(proof_options, ®ister_init), - register::NUM_PREPROCESSED_COLS, - ) + let register_commitment = vkey.map(|vk| vk.register).unwrap_or_else(|| { + register::preprocessed_commitment(proof_options, ®ister_init) + }); + create_register_air(proof_options) + .with_preprocessed(register_commitment, register::NUM_PREPROCESSED_COLS) }; // Every zero-init page shares one preprocessed commitment: OFFSET is // page-relative and INIT is all-zero, so it depends only on @@ -526,7 +673,8 @@ impl VmAirs { let pages: Vec<_> = page_configs .iter() - .map(|config| { + .enumerate() + .map(|(index, config)| { let air = create_page_air(proof_options, config.page_base); if config.is_private_input { // Private-input pages: all columns are main trace (not preprocessed). @@ -535,16 +683,21 @@ impl VmAirs { air } else if config.init_values.is_none() { // Zero-init pages: the shared commitment computed once above. + // `vkey.pages` caches the same static value for these slots, + // so the local lookup is equivalent and equally cheap. air.with_preprocessed(zero_init_commitment, page::NUM_PREPROCESSED_COLS) } else { // ELF data pages: INIT is program-specific, so the commitment is // per-page. Prefer a caller-supplied `(page_base, commitment)` - // (recursion guest); otherwise recompute from the ELF. + // (recursion guest), then the vkey's cached per-page root + // (indexed parallel to `page_configs`); otherwise recompute + // from the ELF. let commitment = page_commitments .unwrap_or(&[]) .iter() .find(|(pb, _)| *pb == config.page_base) .map(|(_, c)| *c) + .or_else(|| vkey.and_then(|vk| vk.pages.get(index)).copied()) .unwrap_or_else(|| { page::compute_precomputed_commitment(config, proof_options) }); @@ -613,10 +766,10 @@ impl VmAirs { /// challenge elements. pub(crate) fn replay_transcript_phase_a( airs: &[&dyn AIR], - multi_proof: &MultiProof, + proofs: &[ArchivedStarkProof], transcript: &mut DefaultTranscript, ) -> (FieldElement, FieldElement) { - for (air, proof) in airs.iter().zip(&multi_proof.proofs) { + for (air, proof) in airs.iter().zip(proofs) { if air.is_preprocessed() { transcript.append_bytes(&air.precomputed_commitment()); } @@ -683,15 +836,38 @@ pub(crate) fn compute_commit_bus_offset( /// and before `multi_verify`. pub(crate) fn compute_expected_commit_bus_balance( airs: &[&dyn AIR], - proof: &MultiProof, + proofs: &[ArchivedStarkProof], public_output_bytes: &[u8], start_index: u64, transcript: &mut DefaultTranscript, ) -> Option> { - let (z, alpha) = replay_transcript_phase_a(airs, proof, transcript); + let (z, alpha) = replay_transcript_phase_a(airs, proofs, transcript); compute_commit_bus_offset(public_output_bytes, start_index, &z, &alpha) } +/// Owned-proof convenience over [`compute_expected_commit_bus_balance`]: +/// archives the proof and replays from the archived view. Test helper. +#[cfg(test)] +pub(crate) fn compute_expected_commit_bus_balance_owned( + airs: &[&dyn AIR], + proof: &MultiProof, + public_output_bytes: &[u8], + start_index: u64, + transcript: &mut DefaultTranscript, +) -> Option> { + let proof_bytes = rkyv::to_bytes::(proof).ok()?; + // SAFETY: `proof_bytes` was produced by `rkyv::to_bytes` on the line above. + let archived = + unsafe { rkyv::access_unchecked::>(&proof_bytes) }; + compute_expected_commit_bus_balance( + airs, + archived.proofs.as_slice(), + public_output_bytes, + start_index, + transcript, + ) +} + /// Bind the final cross-epoch GlobalMemory proof to the per-epoch proofs. /// /// The final proof commits one local-to-global sub-table per epoch as its first @@ -847,7 +1023,12 @@ pub fn prove_with_options_and_inputs( let __sp = stark::instruments::span("air_construction"); let table_counts = traces.table_counts(); - let airs = VmAirs::new( + // Derive the vkey before AIR construction so each preprocessed + // commitment is computed once and reused. + let vkey = + VmVerifyingKey::from_elf_and_options(&program, proof_options, None, &traces.page_configs); + let vk_digest = vkey.compute_digest(); + let airs = VmAirs::new_with_vkey( &program, proof_options, false, @@ -858,6 +1039,7 @@ pub fn prove_with_options_and_inputs( None, None, None, + Some(&vkey), ); #[cfg(feature = "instruments")] @@ -880,7 +1062,7 @@ pub fn prove_with_options_and_inputs( let mut transcript = DefaultTranscript::::new(&[]); absorb_statement( &mut transcript, - StatementKind::Monolithic, + StatementKind::Monolithic { vk_digest }, elf_bytes, &traces.public_output_bytes, &table_counts, @@ -931,6 +1113,7 @@ pub fn prove_with_options_and_inputs( table_counts, public_output: traces.public_output_bytes.clone(), num_private_input_pages, + vk_digest, }) } @@ -984,20 +1167,195 @@ pub fn verify_with_options( decode_commitment: Option, page_commitments: Option<&[(u64, Commitment)]>, ) -> Result { + verify_with_options_with_vkey( + vm_proof, + elf_bytes, + proof_options, + decode_commitment, + page_commitments, + None, + ) +} + +/// Same as [`verify_with_options`] but accepts a precomputed +/// [`VmVerifyingKey`], skipping the preprocessed-commitment recomputation. +/// +/// # Security +/// +/// The vkey is TRUSTED input. Passing one supplied by the prover (as the +/// recursion guest does) proves nothing by itself: a coordinated prover can +/// commit to a forged preprocessed table and ship a matching vkey, and the +/// transcript stays self-consistent. Soundness requires the caller's output +/// to bind `vkey.compute_digest()` (the recursion guest commits it) so an +/// outer verifier can compare it against a digest derived from trusted +/// inputs. See `vkey.rs`. +pub fn verify_with_options_with_vkey( + vm_proof: &VmProof, + elf_bytes: &[u8], + proof_options: &ProofOptions, + decode_commitment: Option, + page_commitments: Option<&[(u64, Commitment)]>, + vkey: Option<&VmVerifyingKey>, +) -> Result { + // The verifier has a single implementation over the rkyv wire format + // (`verify_archived_parts`); an owned in-memory proof is archived once here + // and read back in place. Proofs arriving as bytes skip this entirely. + let proof_bytes = rkyv::to_bytes::(&vm_proof.proof) + .map_err(|e| Error::Execution(format!("rkyv encode of proof failed: {e}")))?; + // SAFETY: `proof_bytes` was produced by `rkyv::to_bytes` on the line above, + // so it is a valid, aligned archive of this exact type. + let archived = + unsafe { rkyv::access_unchecked::>(&proof_bytes) }; + + verify_archived_parts( + archived.proofs.as_slice(), + &vm_proof.table_counts, + &vm_proof.runtime_page_ranges, + vm_proof.num_private_input_pages, + &vm_proof.public_output, + &vm_proof.vk_digest, + elf_bytes, + proof_options, + decode_commitment, + page_commitments, + vkey, + ) + .map(|v| v.ok) +} + +/// Result of a recursion-blob verification: the verdict plus the values the +/// recursion guest commits (`vk_digest ‖ public_output`), read from the blob. +pub struct RecursionVerification<'a> { + /// Whether the inner proof verified. + pub ok: bool, + /// Digest of the verifying key that was actually used, recomputed from the + /// supplied vkey (and checked against the proof's embedded `vk_digest`). + pub vk_digest: [u8; 32], + /// The inner proof's committed public output (zero-copy from the blob). + pub public_output: &'a [u8], +} + +/// Verify a recursion-input blob produced by [`encode_recursion_input`]. +/// +/// The archive is read **in place**: the STARK proof is verified straight from +/// the blob with no deserialization and no per-field allocation. Only tiny +/// metadata (options, vkey, table counts, page ranges) is materialized. +pub fn verify_recursion_blob(blob: &[u8]) -> Result, Error> { + use rkyv::rancor::Error as RkyvError; + + // Validate + strip the magic/version prefix. In the guest the returned + // slice starts at the 16-aligned archive base (aligned payload base + + // 16-byte prefix), so the in-place doubleword loads do not trap. + let archive_bytes = recursion_archive_bytes(blob).ok_or_else(|| { + Error::Execution(String::from("recursion blob: bad magic or version")) + })?; + + // A host caller's buffer carries no alignment guarantee (`Vec` is + // align-1, so `&blob[12..]` is typically only 4-aligned) — in-place access + // there would be UB. Fall back to one aligned copy when the base is + // misaligned; the guest path is aligned by construction and stays + // zero-copy. + let mut aligned_fallback = rkyv::util::AlignedVec::<{ RECURSION_INPUT_ALIGN }>::new(); + let archive: &[u8] = + if (archive_bytes.as_ptr() as usize).is_multiple_of(RECURSION_INPUT_ALIGN) { + archive_bytes + } else { + aligned_fallback.extend_from_slice(archive_bytes); + &aligned_fallback + }; + + // SAFETY: `archive` is RECURSION_INPUT_ALIGN-aligned (checked above). No + // structural validation is performed. On the host, callers must pass blobs + // produced by our own `encode_recursion_input`. In the recursion guest the + // blob is PROVER-SUPPLIED: a malformed archive makes the in-place reads + // hit arbitrary guest addresses, which the VM memory model defines (reads + // of unmapped cells return 0), so the outcome is garbage field elements + // that fail the cryptographic checks or an in-guest panic — verification + // failure either way, never a false accept. + let archived = unsafe { rkyv::access_unchecked::(archive) }; + + // Materialize only the small metadata; the proof stays in the buffer. + let options: ProofOptions = rkyv::deserialize::(&archived.options) + .map_err(|e| Error::Execution(format!("rkyv deserialize options failed: {e}")))?; + let vkey: VmVerifyingKey = rkyv::deserialize::(&archived.vkey) + .map_err(|e| Error::Execution(format!("rkyv deserialize vkey failed: {e}")))?; + let table_counts: TableCounts = + rkyv::deserialize::(&archived.vm_proof.table_counts) + .map_err(|e| Error::Execution(format!("rkyv deserialize table_counts failed: {e}")))?; + let runtime_page_ranges: Vec = + rkyv::deserialize::, RkyvError>(&archived.vm_proof.runtime_page_ranges) + .map_err(|e| Error::Execution(format!("rkyv deserialize page ranges failed: {e}")))?; + let num_private_input_pages = archived.vm_proof.num_private_input_pages.to_native() as usize; + // Bytes read straight from the archived buffer (zero-copy). + let inner_elf: &[u8] = archived.inner_elf.as_slice(); + let public_output: &[u8] = archived.vm_proof.public_output.as_slice(); + // Rebase the returned output slice onto the caller's buffer: `archive` may + // be the aligned fallback copy, whose lifetime ends with this call. Same + // bytes at the same offsets in both buffers. + let public_output_offset = public_output.as_ptr() as usize - archive.as_ptr() as usize; + + let verification = verify_archived_parts( + archived.vm_proof.proof.proofs.as_slice(), + &table_counts, + &runtime_page_ranges, + num_private_input_pages, + public_output, + &archived.vm_proof.vk_digest, + inner_elf, + &options, + None, + None, + Some(&vkey), + )?; + + Ok(RecursionVerification { + ok: verification.ok, + vk_digest: verification.vk_digest, + public_output: &archive_bytes[public_output_offset..public_output_offset + public_output.len()], + }) +} + +/// Internal verdict of [`verify_archived_parts`]. +struct ArchivedVerification { + ok: bool, + vk_digest: [u8; 32], +} + +/// The single VM-proof verification implementation: verifies a STARK proof +/// whose sub-proofs are read in place from an rkyv archive, given the proof's +/// metadata fields. Both [`verify_with_options_with_vkey`] (owned proof, +/// archived once) and [`verify_recursion_blob`] (guest blob, zero-copy) funnel +/// here. +#[allow(clippy::too_many_arguments)] +fn verify_archived_parts( + archived_proofs: &[ArchivedStarkProof], + table_counts: &TableCounts, + runtime_page_ranges: &[RuntimePageRange], + num_private_input_pages: usize, + public_output: &[u8], + vk_digest_in_proof: &[u8; 32], + elf_bytes: &[u8], + proof_options: &ProofOptions, + decode_commitment: Option, + page_commitments: Option<&[(u64, Commitment)]>, + vkey: Option<&VmVerifyingKey>, +) -> Result { // Validate table_counts before constructing AIRs. // A malicious prover could set counts to 0, removing entire constraint sets. - vm_proof.table_counts.validate()?; + table_counts.validate()?; // Bound num_private_input_pages before allocating PageConfigs. // MAX_PRIVATE_INPUT_SIZE fits in ~257 pages of DEFAULT_PAGE_SIZE. { use crate::tables::page::DEFAULT_PAGE_SIZE; - use executor::vm::memory::MAX_PRIVATE_INPUT_SIZE; - let max_pages = (MAX_PRIVATE_INPUT_SIZE as usize + 4).div_ceil(DEFAULT_PAGE_SIZE) + 1; - if vm_proof.num_private_input_pages > max_pages { + use executor::vm::memory::{MAX_PRIVATE_INPUT_SIZE, PRIVATE_INPUT_PAYLOAD_OFFSET}; + let max_pages = (MAX_PRIVATE_INPUT_SIZE as usize + + PRIVATE_INPUT_PAYLOAD_OFFSET as usize) + .div_ceil(DEFAULT_PAGE_SIZE) + + 1; + if num_private_input_pages > max_pages { return Err(Error::InvalidTableCounts(format!( - "num_private_input_pages ({}) exceeds max ({max_pages})", - vm_proof.num_private_input_pages, + "num_private_input_pages ({num_private_input_pages}) exceeds max ({max_pages})", ))); } } @@ -1005,38 +1363,79 @@ pub fn verify_with_options( let program = Elf::load(elf_bytes).map_err(|e| Error::ElfLoad(format!("{e}")))?; let page_configs = Traces::page_configs_from_elf_and_runtime( &program, - &vm_proof.runtime_page_ranges, - vm_proof.num_private_input_pages, + runtime_page_ranges, + num_private_input_pages, ); + // Validate the vkey before constructing AIRs: `vk.pages` is indexed + // parallel to `page_configs` (a short vec would panic), and options are + // checked because query count / grinding factor affect soundness but no + // commitment. With no caller vkey, derive one from the trusted ELF. + let owned_vkey; + let vkey = match vkey { + Some(vk) => { + if vk.version != crate::vkey::VKEY_VERSION { + return Err(Error::InvalidVerifyingKey(format!( + "vkey version {} != expected {}", + vk.version, + crate::vkey::VKEY_VERSION, + ))); + } + if vk.pages.len() != page_configs.len() { + return Err(Error::InvalidVerifyingKey(format!( + "vkey has {} page commitments but the proof requires {}", + vk.pages.len(), + page_configs.len(), + ))); + } + if vk.options != *proof_options { + return Err(Error::InvalidVerifyingKey( + "vkey options do not match the verification options".into(), + )); + } + vk + } + None => { + owned_vkey = + VmVerifyingKey::from_elf_and_options(&program, proof_options, None, &page_configs); + &owned_vkey + } + }; + let vk_digest = vkey.compute_digest(); + if *vk_digest_in_proof != vk_digest { + return Err(Error::InvalidVerifyingKey( + "proof vk_digest does not match the verifying key's digest".into(), + )); + } + // Cross-check: table_counts must match the number of sub-proofs. // FIXED_TABLE_COUNT always-present tables, plus page tables. - let expected_proof_count = - vm_proof.table_counts.total() + FIXED_TABLE_COUNT + page_configs.len(); - if expected_proof_count != vm_proof.proof.proofs.len() { + let expected_proof_count = table_counts.total() + FIXED_TABLE_COUNT + page_configs.len(); + if expected_proof_count != archived_proofs.len() { return Err(Error::InvalidTableCounts(format!( "table_counts total ({}) + {FIXED_TABLE_COUNT} fixed + {} pages = {}, but proof contains {} sub-proofs", - vm_proof.table_counts.total(), + table_counts.total(), page_configs.len(), expected_proof_count, - vm_proof.proof.proofs.len(), + archived_proofs.len(), ))); } - let airs = VmAirs::new( + let airs = VmAirs::new_with_vkey( &program, proof_options, false, &page_configs, - &vm_proof.table_counts, + table_counts, decode_commitment, true, None, page_commitments, None, + Some(vkey), ); - // Recompute the COMMIT output bus offset from VmProof.public_output. + // Recompute the COMMIT output bus offset from the public output. // If public_output was tampered, the recomputed offset won't match the // actual bus total in the proof, and multi_verify will reject. let air_refs = airs.air_refs(); @@ -1047,12 +1446,12 @@ pub fn verify_with_options( let mut transcript = DefaultTranscript::::new(&[]); absorb_statement( &mut transcript, - StatementKind::Monolithic, + StatementKind::Monolithic { vk_digest }, elf_bytes, - &vm_proof.public_output, - &vm_proof.table_counts, - vm_proof.num_private_input_pages, - &vm_proof.runtime_page_ranges, + public_output, + table_counts, + num_private_input_pages, + runtime_page_ranges, ); // Fork the post-absorb state: the replay helper advances through Phase A @@ -1061,22 +1460,31 @@ pub fn verify_with_options( let mut transcript_for_replay = transcript.clone(); let expected_bus_balance = match compute_expected_commit_bus_balance( &air_refs, - &vm_proof.proof, - &vm_proof.public_output, + archived_proofs, + public_output, // Monolithic proof: commits are indexed from 0. 0, &mut transcript_for_replay, ) { Some(balance) => balance, - None => return Ok(false), + None => { + return Ok(ArchivedVerification { + ok: false, + vk_digest, + }); + } }; - Ok(Verifier::multi_verify( + stark::profile_markers::step_marker::<{ stark::profile_markers::STEP_AIRS_AND_BUS_BALANCE_DONE }>( + ); + + let ok = Verifier::multi_verify_archived( &air_refs, - &vm_proof.proof, + archived_proofs, &mut transcript, &expected_bus_balance, - )) + ); + Ok(ArchivedVerification { ok, vk_digest }) } /// Prove and verify in one call (convenience). diff --git a/prover/src/statement.rs b/prover/src/statement.rs index cca961be5..ca2ff2459 100644 --- a/prover/src/statement.rs +++ b/prover/src/statement.rs @@ -16,7 +16,8 @@ use crate::test_utils::E; use crate::{RuntimePageRange, TableCounts}; /// Domain-separation tag. Bump the suffix (`_V2`, ...) on any encoding change. -const DOMAIN_TAG: &[u8] = b"LAMBDAVM_STARK_STATEMENT_V2"; +/// Domain-separation tag. Bump the suffix (`_V3`, ...) on any encoding change. +const DOMAIN_TAG: &[u8] = b"LAMBDAVM_STARK_STATEMENT_V3"; fn elf_digest(elf: &[u8]) -> [u8; 32] { let mut h = Keccak256::new(); @@ -24,14 +25,15 @@ fn elf_digest(elf: &[u8]) -> [u8; 32] { h.finalize().into() } -/// Which statement is being bound. Selects the leading domain tag and whether an -/// epoch label is appended, so monolithic and continuation-epoch proofs share one -/// function while each starts with its own tag. `Monolithic` reproduces the -/// original encoding byte-for-byte (no label), so existing proofs are unaffected. +/// Which statement is being bound. Selects the leading domain tag and the +/// kind-specific fields, so monolithic and continuation-epoch proofs share one +/// function while each starts with its own tag. #[derive(Clone, Copy)] pub(crate) enum StatementKind { - /// Whole-program (monolithic) proof. - Monolithic, + /// Whole-program (monolithic) proof. Carries the digest of the + /// [`crate::VmVerifyingKey`] (preprocessed commitments + proof options) + /// so every challenge depends on which vkey the proof was made against. + Monolithic { vk_digest: [u8; 32] }, /// One continuation epoch proof, pinned to its position by `epoch_label`. ContinuationEpoch { epoch_label: u64 }, } @@ -48,11 +50,17 @@ pub(crate) fn absorb_statement( // Leading domain tag — distinct per statement kind, so a monolithic proof and // a continuation epoch proof can never share a transcript prefix. let domain_tag = match kind { - StatementKind::Monolithic => DOMAIN_TAG, + StatementKind::Monolithic { .. } => DOMAIN_TAG, StatementKind::ContinuationEpoch { .. } => CONTINUATION_EPOCH_TAG, }; t.append_bytes(domain_tag); + // Fixed 32 bytes, no length prefix needed; the per-kind tags keep + // kind-specific fields unambiguous. + if let StatementKind::Monolithic { vk_digest } = kind { + t.append_bytes(&vk_digest); + } + // ELF: fixed 32-byte digest — no length prefix needed. t.append_bytes(&elf_digest(elf_bytes)); diff --git a/prover/src/tables/local_to_global.rs b/prover/src/tables/local_to_global.rs index ada19baf5..193374056 100644 --- a/prover/src/tables/local_to_global.rs +++ b/prover/src/tables/local_to_global.rs @@ -83,7 +83,7 @@ pub const GENESIS_EPOCH: u64 = 0; pub const MAX_EPOCHS: u64 = 1 << 20; /// A cell's state when an epoch first touches it. -#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct InitClaim { /// Value the cell held when this epoch first touched it. pub value: u64, @@ -94,7 +94,7 @@ pub struct InitClaim { } /// A cell's state at the end of the epoch that touched it. -#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct FiniClaim { /// Value the cell holds at this epoch's end. pub value: u64, @@ -105,7 +105,7 @@ pub struct FiniClaim { } /// The init/fini boundary claims for a single touched cell. -#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] pub struct CellBoundary { pub address: u64, pub init: InitClaim, diff --git a/prover/src/tables/page.rs b/prover/src/tables/page.rs index 2d1059bcc..7e1f7dea5 100644 --- a/prover/src/tables/page.rs +++ b/prover/src/tables/page.rs @@ -328,6 +328,26 @@ pub fn compute_precomputed_commitment(config: &PageConfig, options: &ProofOption root } +/// Returns a page's preprocessed commitment, preferring the cheap path. +/// +/// Zero-init pages (INIT is all-zero) share a single commitment that depends +/// only on `(blowup, coset)`, so they resolve to the static lookup in +/// [`zero_init_preprocessed_commitment`] instead of rebuilding the FFT + +/// Merkle tree. ELF data pages have program-specific INIT and fall through +/// to [`compute_precomputed_commitment`]. This mirrors the per-page choice +/// made in `VmAirs::new_with_vkey`, so a vkey built from this function caches +/// exactly the commitments the verifier expects. +/// +/// Private-input pages have no preprocessed commitment; callers must skip +/// them before calling this. +pub fn precomputed_commitment_cached(config: &PageConfig, options: &ProofOptions) -> Commitment { + if config.init_values.is_none() { + zero_init_preprocessed_commitment(options) + } else { + compute_precomputed_commitment(config, options) + } +} + /// Returns the zero-init PAGE preprocessed commitment. /// /// Looks up `blowup_factor` in [`static_zero_page_commitment`] when diff --git a/prover/src/tables/trace_builder.rs b/prover/src/tables/trace_builder.rs index 93f3ba563..73a42bbfc 100644 --- a/prover/src/tables/trace_builder.rs +++ b/prover/src/tables/trace_builder.rs @@ -1842,12 +1842,15 @@ fn collect_byte_check_ops_for_padding(num_padding_rows: usize) -> Vec Vec { - let len_bytes = (private_input.len() as u32).to_le_bytes(); - len_bytes - .iter() - .chain(private_input.iter()) - .copied() - .collect() + // Mirrors `Memory::store_private_inputs`: `[len: u32 LE][reserved: 12 + // zero bytes][payload]` — the payload base is 16-aligned. + use executor::vm::memory::PRIVATE_INPUT_PAYLOAD_OFFSET; + let mut bytes = + Vec::with_capacity(PRIVATE_INPUT_PAYLOAD_OFFSET as usize + private_input.len()); + bytes.extend_from_slice(&(private_input.len() as u32).to_le_bytes()); + bytes.resize(PRIVATE_INPUT_PAYLOAD_OFFSET as usize, 0); + bytes.extend_from_slice(private_input); + bytes } /// Build the initial-memory image (byte address -> value) from the ELF segments diff --git a/prover/src/tests/local_to_global_bus_tests.rs b/prover/src/tests/local_to_global_bus_tests.rs index 263e3d938..6416b4514 100644 --- a/prover/src/tests/local_to_global_bus_tests.rs +++ b/prover/src/tests/local_to_global_bus_tests.rs @@ -1015,7 +1015,8 @@ fn test_l2g_design_y_orphan_mu_zero_rejects() { /// epoch boundaries) and the resulting multi-epoch L2G chain verifies end-to-end. /// /// The fixture reads 16 bytes of private input from 0xFF000000, then commits -/// bytes 4..12 (8 bytes after the 4-byte length prefix). With `epoch_size_log2=2` +/// payload bytes 0..8 (the payload starts at +16, after the input header). +/// With `epoch_size_log2=2` /// (4 cycles) the 11-cycle program spans three epochs: epoch 0 reads the private-input /// page (touching 0xFF000000..), epoch 1 performs the commit syscall, epoch 2 /// halts. The private-input page's L2G entry (epoch 0 fini → epoch 1+ init) @@ -1028,15 +1029,12 @@ fn test_l2g_design_y_orphan_mu_zero_rejects() { fn test_continuation_private_input_spans_epochs() { let elf_bytes = crate::test_utils::asm_elf_bytes("test_private_input_xpage"); - // 16-byte private input: 4-byte length prefix (=16) + 8 bytes of payload - // that will be committed + 4 padding bytes (the fixture commits bytes 4..12). + // 16-byte private input: the fixture commits payload bytes 0..8. let mut input: Vec = Vec::with_capacity(16); - // Length prefix: 16 as little-endian u32. - input.extend_from_slice(&16u32.to_le_bytes()); // 8-byte payload that will be committed. input.extend_from_slice(&[0x11u8, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]); - // 4 trailing padding bytes (not committed). - input.extend_from_slice(&[0x00u8, 0x00, 0x00, 0x00]); + // 8 trailing padding bytes (not committed). + input.extend_from_slice(&[0u8; 8]); assert_eq!(input.len(), 16); let result = crate::continuation::prove_and_verify_continuation( @@ -1049,11 +1047,11 @@ fn test_continuation_private_input_spans_epochs() { // The continuation must prove and verify without error. let output = result.expect("prove_and_verify_continuation must not error"); - // The fixture commits bytes 4..12 of private input (the 8-byte payload). + // The fixture commits payload bytes 0..8 of the private input. assert_eq!( output.as_deref(), - Some(&input[4..12]), - "committed output must equal private input bytes 4..12" + Some(&input[0..8]), + "committed output must equal private input bytes 0..8" ); } diff --git a/prover/src/tests/mod.rs b/prover/src/tests/mod.rs index 9e650422f..d10336e8d 100644 --- a/prover/src/tests/mod.rs +++ b/prover/src/tests/mod.rs @@ -78,3 +78,5 @@ pub mod templates_tests; pub mod trace_builder_tests; #[cfg(test)] pub mod trace_test_helpers; +#[cfg(test)] +pub mod vkey_tests; diff --git a/prover/src/tests/prove_elfs_tests.rs b/prover/src/tests/prove_elfs_tests.rs index 10013b5ed..4b5f2cbad 100644 --- a/prover/src/tests/prove_elfs_tests.rs +++ b/prover/src/tests/prove_elfs_tests.rs @@ -76,7 +76,7 @@ fn prove_and_verify_vm_minimal(elf: &Elf, traces: &mut Traces) -> bool { // Compute the verifier-side expected COMMIT bus balance from public output bytes let mut replay_transcript = DefaultTranscript::::new(&[]); - let expected_bus_balance = crate::compute_expected_commit_bus_balance( + let expected_bus_balance = crate::compute_expected_commit_bus_balance_owned( &airs.air_refs(), &multi_proof, &traces.public_output_bytes, @@ -136,6 +136,9 @@ fn prove_vm_minimal(elf_bytes: &[u8], private_inputs: &[u8], max_rows: &MaxRowsC table_counts, public_output: traces.public_output_bytes.clone(), num_private_input_pages, + // Minimal proofs skip statement absorption; only verify_vm_minimal + // can check them, so the digest is never read. + vk_digest: [0u8; 32], } } @@ -164,7 +167,7 @@ fn verify_vm_minimal(vm_proof: &VmProof, elf_bytes: &[u8]) -> bool { ); let air_refs = airs.air_refs(); let mut replay_transcript = DefaultTranscript::::new(&[]); - let expected_bus_balance = crate::compute_expected_commit_bus_balance( + let expected_bus_balance = crate::compute_expected_commit_bus_balance_owned( &air_refs, &vm_proof.proof, &vm_proof.public_output, @@ -1375,7 +1378,7 @@ fn test_prove_elfs_test_commit_4_wrong_pages_rejected() { ); let verifier_air_refs = verifier_airs.air_refs(); let mut replay_transcript = DefaultTranscript::::new(&[]); - let expected_bus_balance = crate::compute_expected_commit_bus_balance( + let expected_bus_balance = crate::compute_expected_commit_bus_balance_owned( &verifier_air_refs, &proof, &traces.public_output_bytes, @@ -2130,7 +2133,7 @@ fn test_deep_stack_runtime_pages_roundtrip() { ); let verifier_air_refs = verifier_airs.air_refs(); let mut replay_transcript = DefaultTranscript::::new(&[]); - let expected_bus_balance = crate::compute_expected_commit_bus_balance( + let expected_bus_balance = crate::compute_expected_commit_bus_balance_owned( &verifier_air_refs, &proof, &traces.public_output_bytes, @@ -2203,7 +2206,7 @@ fn test_deep_stack_missing_pages_rejected() { ); let verifier_air_refs = verifier_airs.air_refs(); let mut replay_transcript = DefaultTranscript::::new(&[]); - let expected_bus_balance = crate::compute_expected_commit_bus_balance( + let expected_bus_balance = crate::compute_expected_commit_bus_balance_owned( &verifier_air_refs, &proof, &traces.public_output_bytes, @@ -2311,7 +2314,7 @@ fn test_heap_alloc_runtime_pages_roundtrip() { ); let verifier_air_refs = verifier_airs.air_refs(); let mut replay_transcript = DefaultTranscript::::new(&[]); - let expected_bus_balance = crate::compute_expected_commit_bus_balance( + let expected_bus_balance = crate::compute_expected_commit_bus_balance_owned( &verifier_air_refs, &proof, &traces.public_output_bytes, @@ -2663,7 +2666,7 @@ fn test_prove_private_input_xpage() { let input: Vec = (0u8..16).collect(); let proof = prove_vm_minimal(&elf_bytes, &input, &Default::default()); assert!(verify_vm_minimal(&proof, &elf_bytes), "proof should verify"); - assert_eq!(proof.public_output, input[4..12].to_vec()); + assert_eq!(proof.public_output, input[0..8].to_vec()); } /// Same ASM, different input values — output depends on input. @@ -2676,7 +2679,7 @@ fn test_prove_private_input_different_values() { ]; let proof = prove_vm_minimal(&elf_bytes, &input, &Default::default()); assert!(verify_vm_minimal(&proof, &elf_bytes), "proof should verify"); - assert_eq!(proof.public_output, input[4..12].to_vec()); + assert_eq!(proof.public_output, input[0..8].to_vec()); } /// End-to-end: EF zkVM IO interface — demo guest reads its private input via @@ -2865,8 +2868,8 @@ fn test_proof_does_not_contain_private_input_field() { // not the actual bytes. Verify the proof's public fields don't contain them. assert_eq!(vm_proof.num_private_input_pages, 1); // public_output is the committed output, NOT the private input. - // It should contain bytes [4..12] of the input (what the ASM program commits). - assert_eq!(vm_proof.public_output, input[4..12].to_vec()); + // It should contain bytes [0..8] of the input (what the ASM program commits). + assert_eq!(vm_proof.public_output, input[0..8].to_vec()); // No `private_input` field exists — this is enforced by the type system, // but explicitly document that the proof carries only the page count. assert!( @@ -2911,7 +2914,7 @@ fn test_count_elements_nonzero() { /// not terminate, so it is proven with the HALT table excluded (`include_halt = false`). #[test] fn test_prove_first_epoch_without_halt() { - use crate::compute_expected_commit_bus_balance; + use crate::compute_expected_commit_bus_balance_owned; use crate::tables::trace_builder::build_initial_image; use crate::test_utils::asm_elf_bytes; @@ -2969,7 +2972,7 @@ fn test_prove_first_epoch_without_halt() { .expect("first epoch failed to prove"); let mut replay = DefaultTranscript::::new(&[]); - let expected_bus_balance = compute_expected_commit_bus_balance( + let expected_bus_balance = compute_expected_commit_bus_balance_owned( &airs.air_refs(), &multi_proof, &traces.public_output_bytes, @@ -2994,7 +2997,7 @@ fn test_prove_first_epoch_without_halt() { /// does not terminate (HALT excluded). #[test] fn test_prove_second_epoch_from_snapshot() { - use crate::compute_expected_commit_bus_balance; + use crate::compute_expected_commit_bus_balance_owned; use crate::tables::register; use crate::test_utils::asm_elf_bytes; @@ -3053,7 +3056,7 @@ fn test_prove_second_epoch_from_snapshot() { .expect("second epoch failed to prove"); let mut replay = DefaultTranscript::::new(&[]); - let expected_bus_balance = compute_expected_commit_bus_balance( + let expected_bus_balance = compute_expected_commit_bus_balance_owned( &airs.air_refs(), &multi_proof, &traces.public_output_bytes, @@ -3079,7 +3082,7 @@ fn test_prove_second_epoch_from_snapshot() { /// will bind to. The cross-epoch GlobalMemory matching is proven separately. #[test] fn test_epoch_proof_commits_l2g() { - use crate::compute_expected_commit_bus_balance; + use crate::compute_expected_commit_bus_balance_owned; use crate::tables::local_to_global; use crate::tables::register; use crate::tables::trace_builder::{build_initial_image, epoch_touched_cells}; @@ -3160,7 +3163,7 @@ fn test_epoch_proof_commits_l2g() { refs.push(&inert_l2g_air); let mut replay = DefaultTranscript::::new(&[]); - let expected_bus_balance = compute_expected_commit_bus_balance( + let expected_bus_balance = compute_expected_commit_bus_balance_owned( &refs, &multi_proof, &traces.public_output_bytes, @@ -3202,7 +3205,7 @@ fn test_epoch_proof_commits_l2g() { /// argument. #[test] fn test_continuation_pipeline_end_to_end() { - use crate::compute_expected_commit_bus_balance; + use crate::compute_expected_commit_bus_balance_owned; use crate::tables::local_to_global; use crate::tables::register; use crate::tables::trace_builder::{build_initial_image, epoch_touched_cells}; @@ -3312,7 +3315,7 @@ fn test_continuation_pipeline_end_to_end() { let mut refs = airs.air_refs(); refs.push(&inert_l2g_air); let mut replay = DefaultTranscript::::new(&[]); - let expected_bus_balance = compute_expected_commit_bus_balance( + let expected_bus_balance = compute_expected_commit_bus_balance_owned( &refs, &multi_proof, &traces.public_output_bytes, @@ -3360,7 +3363,7 @@ fn test_continuation_pipeline_end_to_end() { /// `Memory` bus still nets to zero — L2G has replaced PAGE as the bookend. #[test] fn test_epoch_memory_bus_with_l2g_bookend() { - use crate::compute_expected_commit_bus_balance; + use crate::compute_expected_commit_bus_balance_owned; use crate::tables::local_to_global; use crate::tables::register; use crate::tables::trace_builder::build_initial_image; @@ -3443,7 +3446,7 @@ fn test_epoch_memory_bus_with_l2g_bookend() { let mut refs = airs.air_refs(); refs.push(&l2g_air); let mut replay = DefaultTranscript::::new(&[]); - let expected_bus_balance = compute_expected_commit_bus_balance( + let expected_bus_balance = compute_expected_commit_bus_balance_owned( &refs, &multi_proof, &traces.public_output_bytes, diff --git a/prover/src/tests/recursion_smoke_test.rs b/prover/src/tests/recursion_smoke_test.rs index f072eec53..bc7a0596a 100644 --- a/prover/src/tests/recursion_smoke_test.rs +++ b/prover/src/tests/recursion_smoke_test.rs @@ -1,17 +1,13 @@ -//! End-to-end naive recursion pipeline smoke tests. +//! End-to-end naive recursion pipeline smoke tests: prove an inner program, +//! hand `(VmProof, elf, opts, vkey)` to the in-VM verifier guest, then either prove +//! the guest's execution (`OuterMode::Prove`) or just execute it +//! (`OuterMode::ExecuteOnly`). Guest ELFs come from `make compile-recursion-elfs`. //! -//! Each test: -//! 1. Proves an inner program on the host. -//! 2. Serializes `(VmProof, inner_elf, opts)` with postcard. -//! 3. Hands that as private input to the recursion guest. -//! 4. Either **proves** the recursion guest's execution (memory-bounded via -//! continuations) and verifies the outer proof (`OuterMode::Prove`), or -//! merely **executes** the guest in-VM and reads the committed marker -//! straight off the executor's memory (`OuterMode::ExecuteOnly`) — a cheaper -//! tier that skips the LDE/FRI that dominate the full pipeline. -//! -//! The guest ELFs are assumed built by `make compile-recursion-elfs`. +//! Every pipeline host-verifies the inner proof, so building with +//! `--features stark/instruments` makes any of these tests print the verifier's +//! per-step `Time spent:` timings. +use std::ops::ControlFlow; use std::path::PathBuf; fn workspace_root() -> PathBuf { @@ -32,11 +28,8 @@ fn read_guest_elf(root: &std::path::Path, name: &str) -> Vec { }) } -/// Minimum-security FRI parameters: blowup=2, a single FRI query. Security is -/// intentionally terrible — used by the capacity-probing test, where the goal -/// is the smallest possible inner proof, not a sound one. -/// (`GoldilocksCubicProofOptions::with_blowup` derives a query count from a -/// 128-bit target, far more than we want here.) +/// Smallest possible inner proof (blowup=2, 1 query). Intentionally insecure — +/// for the cheap diagnostics, not soundness. const MIN_PROOF_OPTIONS: stark::proof::options::ProofOptions = stark::proof::options::ProofOptions { blowup_factor: 2, @@ -45,17 +38,15 @@ const MIN_PROOF_OPTIONS: stark::proof::options::ProofOptions = grinding_factor: 1, }; -/// Prove `inner_elf` (fed `inner_input`) under `opts`, then package -/// `(proof, elf, opts)` into the postcard blob the recursion guest consumes as -/// its private input. `tag` prefixes the progress lines. Returns the inner -/// proof — callers that re-verify it on the host need it — next to the encoded -/// blob. +/// Prove `inner_elf` under `opts` and rkyv-encode a `RecursionInput` into the +/// guest's private-input blob (magic/version prefix + archive). Returns the +/// proof and the blob. fn prove_inner_and_encode_blob( tag: &str, inner_elf: &[u8], inner_input: &[u8], opts: &stark::proof::options::ProofOptions, -) -> (crate::VmProof, Vec) { +) -> (crate::VmProof, rkyv::util::AlignedVec<16>) { eprintln!( "[{tag}] proving inner (blowup={}, fri_queries={}) ...", opts.blowup_factor, opts.fri_number_of_queries @@ -68,35 +59,36 @@ fn prove_inner_and_encode_blob( ) .expect("inner prove should succeed"); - let blob = - postcard::to_allocvec(&(&inner_proof, &inner_elf, opts)).expect("postcard encode failed"); - eprintln!("[{tag}] postcard blob: {} bytes", blob.len()); - (inner_proof, blob) + let elf_for_vkey = executor::elf::Elf::load(inner_elf).expect("ELF load failed"); + let page_configs = crate::tables::trace_builder::Traces::page_configs_from_elf_and_runtime( + &elf_for_vkey, + &inner_proof.runtime_page_ranges, + inner_proof.num_private_input_pages, + ); + let vkey = + crate::VmVerifyingKey::from_elf_and_options(&elf_for_vkey, opts, None, &page_configs); + let input = crate::RecursionInput { + vm_proof: inner_proof, + inner_elf: inner_elf.to_vec(), + options: opts.clone(), + vkey, + }; + let blob = crate::encode_recursion_input(&input).expect("encode recursion input"); + eprintln!("[{tag}] rkyv blob: {} bytes", blob.len()); + (input.vm_proof, blob) } -/// How far to take the recursion guest after it has been handed the inner -/// proof. The guest under test is the verifier either way — this only chooses -/// whether we also prove the guest's own execution. +/// Whether to also prove the guest's own execution after handing it the proof. #[derive(Clone, Copy, Debug)] enum OuterMode { - /// Execute the guest in-VM and read the committed marker straight off the - /// executor's memory. Streams logs via `Executor::resume()` and never - /// builds a `Traces`, so footprint stays bounded to the VM's touched - /// memory + instruction cache. Skips the LDE/FRI of the full pipeline entirely. + /// Execute in-VM, read the committed marker off memory; no LDE/FRI. ExecuteOnly, - /// Prove the guest's execution via continuations, then verify the outer - /// proof on the host. `prove_and_verify_continuation` retains every epoch's - /// STARK proof in the bundle before verifying, so peak RAM grows with epoch - /// count. Heavy — excluded from CI, run manually. A future verify-one-and- - /// discard API extension would make this memory-friendlier. + /// Prove the execution (memory-bounded via continuations) and verify on host. Prove, } -/// Execute the recursion guest in-VM on `blob` and return the bytes it -/// committed (the success marker the in-VM verifier emits). -/// -/// Streams execution via `Executor::resume()`. The committed marker is -/// read directly off the executor's memory. This avoids OOMs. +/// Execute the recursion guest in-VM on `blob` and return its committed bytes, +/// read straight off the executor's memory after a streamed run. fn execute_outer_and_commit(label: &str, recursion_elf_bytes: &[u8], blob: &[u8]) -> Vec { use executor::elf::Elf; use executor::vm::execution::Executor; @@ -105,12 +97,11 @@ fn execute_outer_and_commit(label: &str, recursion_elf_bytes: &[u8], blob: &[u8] let program = Elf::load(recursion_elf_bytes).expect("load recursion elf"); let mut executor = Executor::new(&program, blob.to_vec()).expect("executor new"); - // Drain chunks to completion without retaining logs or building a trace. - while executor - .resume() - .expect("recursion guest execution failed (verify panicked in-VM?)") - .is_some() - {} + let (total_cycles, exec_time) = drive_executor( + &mut executor, + |_log| ControlFlow::Continue(()), + |_, _, _| {}, + ); let committed = executor .finish() @@ -118,7 +109,7 @@ fn execute_outer_and_commit(label: &str, recursion_elf_bytes: &[u8], blob: &[u8] .memory_values; eprintln!( - "[{label}] committed {} bytes: {:?} (as str: {:?})", + "[{label}] {total_cycles} cycles in {exec_time:?}; committed {} bytes: {:?} (as str: {:?})", committed.len(), committed, String::from_utf8_lossy(&committed), @@ -130,9 +121,8 @@ fn execute_outer_and_commit(label: &str, recursion_elf_bytes: &[u8], blob: &[u8] /// trace+LDE stays under the ~16GiB CI runners. const OUTER_EPOCH_SIZE_LOG2: u32 = 16; -/// Prove the recursion guest's execution on `blob` memory-bounded via -/// continuations and verify the bundle on the host, returning the bytes the -/// guest committed. +/// Prove the guest's execution via continuations, verify on host, return the +/// committed bytes. fn prove_outer_and_commit(label: &str, recursion_elf_bytes: &[u8], blob: &[u8]) -> Vec { let opts = crate::GoldilocksCubicProofOptions::with_blowup(2).expect("blowup=2 is always valid"); @@ -152,10 +142,314 @@ fn prove_outer_and_commit(label: &str, recursion_elf_bytes: &[u8], blob: &[u8]) committed } -/// Core pipeline: prove an inner program with the given options, hand the -/// proof+ELF+options to the recursion guest, then take the guest to `mode` -/// (execute-only or full prove) and assert it committed the `[1]` success -/// marker — i.e. the in-VM verifier accepted the inner proof. +/// Stream a guest's execution via `Executor::resume()` without buffering the log +/// stream. `on_log` returns `Break` to stop early; `on_progress` fires per chunk. +/// Returns `(total_cycles, wall_time)`, exact even on an early break. +fn drive_executor( + executor: &mut executor::vm::execution::Executor, + mut on_log: impl FnMut(&executor::vm::logs::Log) -> ControlFlow<()>, + mut on_progress: impl FnMut(usize, u64, std::time::Duration), +) -> (u64, std::time::Duration) { + let start = std::time::Instant::now(); + let mut total_cycles: u64 = 0; + let mut chunks: usize = 0; + while let Some(logs) = executor + .resume() + .expect("executor resume failed (guest panicked in-VM?)") + { + let mut stop = false; + for log in logs { + total_cycles += 1; + if on_log(log).is_break() { + stop = true; + break; + } + } + chunks += 1; + on_progress(chunks, total_cycles, start.elapsed()); + if stop { + break; + } + } + (total_cycles, start.elapsed()) +} + +/// Shared preamble: build the blob (an `empty` inner proof under `opts`), load +/// `guest_name`, and stand up an executor. Returns `(elf_bytes, program, executor)`. +fn setup_guest_run( + label: &str, + guest_name: &str, + opts: &stark::proof::options::ProofOptions, +) -> ( + Vec, + executor::elf::Elf, + executor::vm::execution::Executor, +) { + let root = workspace_root(); + let empty_elf_bytes = read_guest_elf(&root, "empty"); + let guest_elf_bytes = read_guest_elf(&root, guest_name); + + let (_inner_proof, blob) = prove_inner_and_encode_blob(label, &empty_elf_bytes, &[], opts); + + let program = executor::elf::Elf::load(&guest_elf_bytes).expect("ELF load failed"); + assert_ne!( + program.entry_point, 0, + "{guest_name} ELF has entry_point=0 — build artifact is malformed" + ); + let executor = + executor::vm::execution::Executor::new(&program, blob.to_vec()).expect("Executor::new failed"); + (guest_elf_bytes, program, executor) +} + +/// Demangled enclosing-function name for a PC via the ELF symbol table; +/// `` if none covers it. No file:line (symtab has no DWARF). +fn resolve_pc(symbols: &executor::elf::SymbolTable, pc: u64) -> String { + symbols.lookup(pc).map_or_else( + || "".to_string(), + |s| executor::flamegraph::demangle(&s.name), + ) +} + +/// Verifier sub-steps in execution order, keyed by `stark::profile_markers::STEP_*` +/// value. `run_profile` buckets cycles by the latest marker observed so far +/// (`decode_step_marker`, defaulting to bucket 0 until the first marker fires), +/// so `multi_verify`'s per-table `3,4,5,6` repetition re-attributes cycles to +/// the correct step on each table's `6->3` transition instead of latching at 6. +const STEP_LABELS: [&str; 7] = [ + "0. setup (alloc init + blob prefix check)", + "1. airs_and_bus_balance (Elf::load/VmAirs::new preprocessed FFT+Merkle/bus balance)", + "2. multi_verify setup (transcript replay phase A/B, per-table fork)", + "3. step 1: replay_rounds_after_round_1", + "4. step 2: verify_claimed_composition_polynomial", + "5. step 3: verify_fri", + "6. step 4: verify_trace_and_composition_openings (+ wrap-up)", +]; + +/// `blowup=8` (128-bit, multi-query) options for the `multiquery` variants. +fn blowup8() -> stark::proof::options::ProofOptions { + crate::GoldilocksCubicProofOptions::with_blowup(8).expect("blowup=8 is always valid") +} + +/// Short per-step tag for the function table, keyed by the same bucket index +/// used in `STEP_LABELS`/`buckets`. +fn step_tag(bucket: u8) -> &'static str { + match bucket { + 0 => "setup", + 1 => "airs_bus_balance", + 2 => "multi_verify_setup", + 3 => "step1:replay", + 4 => "step2:claimed", + 5 => "step3:fri", + 6 => "step4:openings", + _ => "?", + } +} + +/// Print one top-25 table: `rows` is `(name, cycles, distinct_pcs)`, already +/// unsorted; `denom_cycles` is the denominator for percentages — the global +/// total for the all-steps table, but *that step's own total* for a per-step +/// table, so `%`/`cum %` show what dominates within that step (a `keccak` +/// that's 90% of a cheap step should read as 90%, not as a fraction of a +/// percent of the whole run). +fn print_top25_table(rows: &mut [(String, u64, u64)], denom_cycles: u64) { + rows.sort_unstable_by_key(|(_name, cycles, _pcs)| std::cmp::Reverse(*cycles)); + let pct = |n: u64| 100.0 * (n as f64) / (denom_cycles as f64); + eprintln!(" rank cycles % cum % PCs function"); + let mut cumulative: u64 = 0; + for (rank, (name, cycles, pcs)) in rows.iter().take(25).enumerate() { + cumulative += cycles; + eprintln!( + " {:>4} {:>14} {:>6.2}% {:>6.2}% {:>5} {}", + rank + 1, + cycles, + pct(*cycles), + pct(cumulative), + pcs, + name, + ); + } +} + +/// Print the global top-25 functions by cycle count, then one top-25 table +/// per verifier step — so e.g. how much of `step4:openings` is spent in +/// `keccak` is visible at a glance, instead of only the function's total +/// across all steps. +fn print_function_table( + symbols: &executor::elf::SymbolTable, + pc_hist: std::collections::HashMap<(u64, u8), u64>, + total_cycles: u64, +) { + let mut by_function: std::collections::HashMap = + std::collections::HashMap::new(); + let mut by_function_per_step: std::collections::HashMap< + u8, + std::collections::HashMap, + > = std::collections::HashMap::new(); + let mut unique_pcs: std::collections::HashSet = std::collections::HashSet::new(); + for ((pc, bucket), count) in &pc_hist { + unique_pcs.insert(*pc); + let name = resolve_pc(symbols, *pc); + + let entry = by_function.entry(name.clone()).or_insert((0, 0)); + entry.0 += *count; // cycles + entry.1 += 1; // distinct PCs folded into this function + + let step_entry = by_function_per_step + .entry(*bucket) + .or_default() + .entry(name) + .or_insert((0, 0)); + step_entry.0 += *count; + step_entry.1 += 1; + } + + eprintln!(" Unique PCs : {}", unique_pcs.len()); + eprintln!(); + eprintln!( + " Top 25 functions by cycle count (aggregated over their PCs, all steps; % of total cycles):" + ); + let mut rows: Vec<(String, u64, u64)> = by_function + .into_iter() + .map(|(name, (cycles, pcs))| (name, cycles, pcs)) + .collect(); + print_top25_table(&mut rows, total_cycles); + + for bucket in 0u8..STEP_LABELS.len() as u8 { + let Some(by_step_function) = by_function_per_step.remove(&bucket) else { + continue; + }; + let step_total: u64 = by_step_function.values().map(|(cycles, _pcs)| cycles).sum(); + eprintln!(); + eprintln!( + " Top 25 functions by cycle count — step {} (% of this step's {} cycles):", + step_tag(bucket), + step_total, + ); + let mut rows: Vec<(String, u64, u64)> = by_step_function + .into_iter() + .map(|(name, (cycles, pcs))| (name, cycles, pcs)) + .collect(); + print_top25_table(&mut rows, step_total); + } +} + +/// Print the per-verifier-step cycle bucketing (`buckets[0]` = setup). +fn print_step_breakdown(buckets: &[u64; 7], total_cycles: u64) { + eprintln!(); + eprintln!(" Per-step cycle breakdown (latest-marker state machine):"); + eprintln!(" {:<70} {:>14} {:>7}", "bucket", "cycles", "%"); + for (label, cycles) in STEP_LABELS.iter().zip(buckets.iter()) { + let pct = if total_cycles > 0 { + 100.0 * (*cycles as f64) / (total_cycles as f64) + } else { + 0.0 + }; + eprintln!(" {:<60} {:>14} {:>6.2}%", label, cycles, pct); + } +} + +/// Single-pass execute-only profiler. Always prints total cycles, the +/// per-step cycle breakdown (marker decode is cheap — one `InstructionCache` +/// lookup per cycle), and a rough trace/LDE estimate; with `detailed`, also +/// the top-25 functions table (needs a `pc_hist` HashMap, so gated). +fn run_profile( + guest_name: &str, + progress_stride: usize, + opts: stark::proof::options::ProofOptions, + detailed: bool, +) { + use std::collections::HashMap; + + let (guest_elf_bytes, program, mut executor) = setup_guest_run("profile", guest_name, &opts); + let symbols = executor::elf::SymbolTable::parse(&guest_elf_bytes); + let instructions = executor::vm::execution::InstructionCache::new(&program.data) + .expect("instruction cache build failed"); + + let mut pc_hist: HashMap<(u64, u8), u64> = HashMap::new(); + let mut buckets = [0u64; 7]; + let bucket = std::cell::Cell::new(0u8); + let unique = std::cell::Cell::new(0usize); + + eprintln!( + "[profile] executing {guest_name} guest ({}) ...", + if detailed { + "histogram + steps" + } else { + "steps" + } + ); + let (total_cycles, exec_time) = drive_executor( + &mut executor, + |log| { + let pc = log.current_pc; + + if let Some(marker) = executor::vm::execution::decode_step_marker(&instructions, pc) { + bucket.set(marker as u8); + } + buckets[bucket.get() as usize] += 1; + + if detailed { + *pc_hist.entry((pc, bucket.get())).or_insert(0) += 1; + unique.set(pc_hist.len()); + } + + ControlFlow::Continue(()) + }, + |chunks, cycles, elapsed| { + if chunks.is_multiple_of(progress_stride) { + if detailed { + eprintln!( + "[profile] ... {chunks} chunks, {cycles} cycles, {} unique PCs, bucket={}, {elapsed:?}", + unique.get(), + bucket.get(), + ); + } else { + eprintln!( + "[profile] ... {chunks} chunks, {cycles} cycles, bucket={}, {elapsed:?}", + bucket.get(), + ); + } + } + }, + ); + + eprintln!(); + eprintln!("============================================================"); + eprintln!( + " {} GUEST PROFILE (blowup={}, {} queries)", + guest_name.to_uppercase(), + opts.blowup_factor, + opts.fri_number_of_queries, + ); + eprintln!("============================================================"); + eprintln!(" Total cycles : {total_cycles}"); + eprintln!(" Exec time : {exec_time:?}"); + eprintln!(); + eprintln!(" Rough trace/LDE size if this guest were proven:"); + let approx_columns = 250u64; + let main_trace_bytes = total_cycles * approx_columns * 8; + eprintln!( + " main trace : ~{:.2} GB ({total_cycles} cycles × ~{approx_columns} cols × 8 B)", + main_trace_bytes as f64 / 1e9, + ); + eprintln!( + " main LDE (blowup=2) : ~{:.2} GB (+aux ≈ 50% more → peak ≈ 2-3× LDE)", + (main_trace_bytes * 2) as f64 / 1e9, + ); + + eprintln!(); + print_step_breakdown(&buckets, total_cycles); + if detailed { + eprintln!(); + print_function_table(&symbols, pc_hist, total_cycles); + } + eprintln!("============================================================"); +} + +/// Core pipeline: prove the inner program, run the guest to `mode`, assert it +/// committed `vk_digest ‖ inner public output` — the outer-verifier check: +/// the digest of the vkey used in-guest must match one derived on the host +/// from the trusted inner ELF. fn run_recursion_pipeline_with_options( label: &str, inner_elf_bytes: &[u8], @@ -194,16 +488,16 @@ fn run_recursion_pipeline_with_options( OuterMode::Prove => prove_outer_and_commit(label, &recursion_elf_bytes, &blob), }; + let mut expected = inner_proof.vk_digest.to_vec(); + expected.extend_from_slice(&inner_proof.public_output); assert_eq!( - committed, - vec![1u8], - "recursion guest must commit the [1] success marker (in-VM verify accepted)" + committed, expected, + "recursion guest must commit vk_digest ‖ inner public output" ); - eprintln!("[{label}] guest committed [1]: in-VM verify accepted ✓"); + eprintln!("[{label}] guest committed vk_digest ‖ output: in-VM verify accepted ✓"); } -/// Convenience wrapper using `blowup=8` for the inner proof — the default for -/// the `empty` and `fibonacci` cases, chosen to keep outer-prove memory tractable. +/// `run_recursion_pipeline_with_options` with `blowup=8` (the `empty`/`fibonacci` default). fn run_recursion_pipeline( label: &str, inner_elf_bytes: &[u8], @@ -221,43 +515,65 @@ fn run_recursion_pipeline( ); } -/// Reproduce the recursion guest's EXACT path on the host — decode the postcard -/// blob into `(VmProof, Vec, ProofOptions)` and call `verify_with_options`. -/// Cheap regression guard. +/// Verify the blob on the host exactly as the guest does (zero-copy through +/// `verify_recursion_blob`) — a cheap guard on the encode/verify contract +/// without running the VM. Also checks the guest's misaligned read conditions +/// and that a tampered proof is rejected. #[test] #[ignore = "needs prebuilt guest ELF (make compile-recursion-elfs)"] fn test_recursion_blob_decodes_and_verifies_on_host() { let root = workspace_root(); let empty_elf_bytes = read_guest_elf(&root, "empty"); - let (_inner, blob) = + let (inner_proof, blob) = prove_inner_and_encode_blob("roundtrip", &empty_elf_bytes, &[], &MIN_PROOF_OPTIONS); - // Decode exactly as the guest does. - let decoded: Result<(crate::VmProof, Vec, crate::ProofOptions), _> = - postcard::from_bytes(&blob); - let (vm_proof, inner_elf, options) = match decoded { - Ok(t) => t, - Err(e) => panic!("[roundtrip] postcard DECODE failed (this is the guest panic): {e}"), - }; - eprintln!( - "[roundtrip] decode ok: elf {} bytes, blowup {}", - inner_elf.len(), - options.blowup_factor + let verification = crate::verify_recursion_blob(&blob).expect("verify_recursion_blob errored"); + assert!(verification.ok, "zero-copy path must accept a valid proof"); + assert_eq!( + verification.public_output, inner_proof.public_output, + "public output must round-trip through the blob" + ); + assert_eq!( + verification.vk_digest, inner_proof.vk_digest, + "vk digest must match the proof's" ); - match crate::verify_with_options(&vm_proof, &inner_elf, &options, None, None) { - Ok(true) => eprintln!("[roundtrip] verify ok=true — guest path is sound"), - Ok(false) => panic!( - "[roundtrip] verify returned FALSE (guest hits assert!(ok)) — proof did not survive the postcard round-trip" - ), - Err(e) => panic!("[roundtrip] verify ERRORED (guest hits .expect): {e:?}"), - } + // The encoder returns a 16-aligned buffer whose archive offset is a + // multiple of 16, so the plain call above exercises the zero-copy aligned + // host path. `verify_recursion_blob` must also accept arbitrary base + // alignment (falling back to one aligned copy): shift the base by 4 to + // exercise the misaligned fallback. + let mut padded: Vec = Vec::with_capacity(blob.len() + 4); + padded.extend_from_slice(&[0u8; 4]); + padded.extend_from_slice(&blob); + let ok_shifted = crate::verify_recursion_blob(&padded[4..]) + .expect("verify_recursion_blob errored on shifted buffer") + .ok; + assert!(ok_shifted, "path must accept the proof from a shifted buffer"); + + // A bad magic must be rejected before the unsafe access. + let mut bad_magic = blob.clone(); + bad_magic[0] ^= 0xFF; + assert!( + crate::verify_recursion_blob(&bad_magic).is_err(), + "bad magic must be rejected" + ); + + // Soundness: a single-byte tamper in the proof payload must make the + // zero-copy verifier reject (Fiat-Shamir / Merkle openings stop matching). + let mut tampered = blob.clone(); + let tamper_idx = tampered.len() - 64; + tampered[tamper_idx] ^= 0x01; + let tampered_result = crate::verify_recursion_blob(&tampered); + assert!( + !matches!(tampered_result.map(|v| v.ok), Ok(true)), + "zero-copy verifier must NOT accept a tampered proof" + ); } // === Execute-only tier ======================================================== -/// Execute-only mirror of `test_recursion_prove_empty`: verify a `blowup=8` -/// proof of the empty program in-VM. +/// Execute-only: verify a `blowup=8` proof of the empty program in-VM. #[test] #[ignore = "slow: runs the in-VM STARK verifier (minutes on CI)"] fn test_recursion_execute_empty() { @@ -271,8 +587,7 @@ fn test_recursion_execute_empty() { ); } -/// Execute-only mirror of `test_recursion_prove_1query`: smallest possible -/// inner proof (blowup=2, 1 query) → least guest work. +/// Execute-only: smallest inner proof (blowup=2, 1 query) → least guest work. #[test] #[ignore = "slow: runs the in-VM STARK verifier (minutes on CI)"] fn test_recursion_execute_1query() { @@ -287,8 +602,69 @@ fn test_recursion_execute_1query() { ); } -/// Execute-only mirror of `test_recursion_prove`: verify a `blowup=8` proof of -/// fibonacci(10) in-VM. +/// Regression test for the marker mechanism itself: every `STEP_*` marker +/// must be observed at least once during a full verifier run, and each +/// transition between consecutive markers must be a valid step in the +/// verifier's state machine. +/// +/// `multi_verify` re-runs `replay_rounds_after_round_1 -> step_2 -> step_3 -> +/// step_4` once per AIR table (see `crypto/stark/src/verifier.rs`), so the +/// full marker sequence isn't monotonic overall — it's `STEP_DECODE_DONE -> +/// STEP_AIRS_AND_BUS_BALANCE_DONE` once each, followed by N repetitions of +/// the `3,4,5,6` cycle (one per table). A transition outside +/// `{1->2, 2->3, 3->4, 4->5, 5->6, 6->3}` means the marker convention broke — +/// wrong immediate decoded, or a stale/mismatched build. +#[test] +#[ignore = "slow: runs the in-VM STARK verifier (minutes on CI)"] +fn test_recursion_step_markers_observed_in_order() { + let (_bytes, program, mut executor) = + setup_guest_run("step-markers", "recursion", &MIN_PROOF_OPTIONS); + let instructions = executor::vm::execution::InstructionCache::new(&program.data) + .expect("instruction cache build failed"); + + let decode_done = stark::profile_markers::STEP_DECODE_DONE; + let airs_ready = stark::profile_markers::STEP_AIRS_AND_BUS_BALANCE_DONE; + let replay = stark::profile_markers::STEP_REPLAY_ROUNDS_AFTER_ROUND_1; + let claimed = stark::profile_markers::STEP_VERIFY_CLAIMED_COMPOSITION_POLYNOMIAL; + let fri = stark::profile_markers::STEP_VERIFY_FRI; + let openings = stark::profile_markers::STEP_VERIFY_TRACE_AND_COMPOSITION_OPENINGS; + + let mut last_marker: Option = None; + let mut seen = std::collections::HashSet::new(); + drive_executor( + &mut executor, + |log| { + if let Some(marker) = + executor::vm::execution::decode_step_marker(&instructions, log.current_pc) + { + let valid_transition = match last_marker { + None => marker == decode_done, + Some(last) if last == decode_done => marker == airs_ready, + Some(last) if last == airs_ready => marker == replay, + Some(last) if last == replay => marker == claimed, + Some(last) if last == claimed => marker == fri, + Some(last) if last == fri => marker == openings, + Some(last) if last == openings => marker == replay, + Some(_) => false, + }; + assert!( + valid_transition, + "invalid step marker transition: {last_marker:?} -> {marker}" + ); + last_marker = Some(marker); + seen.insert(marker); + } + ControlFlow::Continue(()) + }, + |_, _, _| {}, + ); + + for step in [decode_done, airs_ready, replay, claimed, fri, openings] { + assert!(seen.contains(&step), "marker {step} was never observed"); + } +} + +/// Execute-only: verify a `blowup=8` proof of fibonacci(10) in-VM. #[test] #[ignore = "slow: runs the in-VM STARK verifier (minutes on CI)"] fn test_recursion_execute() { @@ -308,8 +684,7 @@ fn test_recursion_execute() { // === Full-prove tier ========================================================== -/// Inner program: empty (halt immediately). Useful for measuring the -/// verifier's intrinsic recursion overhead. +/// Inner program: empty — the verifier's intrinsic recursion overhead. #[test] #[ignore = "slow: memory-bounded continuation prove of the verifier-in-VM"] fn test_recursion_prove_empty() { @@ -323,8 +698,7 @@ fn test_recursion_prove_empty() { ); } -/// Inner program: empty, but with the absolute-minimum FRI parameters -/// (blowup=2, **fri_number_of_queries=1**). For quick profiling only. +/// Inner program: empty, blowup=2/1-query. Quick profiling only. #[test] #[ignore = "slow: memory-bounded continuation prove of the verifier-in-VM"] fn test_recursion_prove_1query() { @@ -340,6 +714,50 @@ fn test_recursion_prove_1query() { ); } +/// Dump the guest's private-input blob to `/tmp/recursion_input.bin` for the +/// CLI's `execute --flamegraph`. +#[test] +#[ignore = "diagnostic: writes recursion private input to /tmp/recursion_input.bin"] +fn test_dump_recursion_input() { + let root = workspace_root(); + let empty_elf_bytes = read_guest_elf(&root, "empty"); + + let (_inner_proof, blob) = + prove_inner_and_encode_blob("dump-input", &empty_elf_bytes, &[], &MIN_PROOF_OPTIONS); + + let path = "/tmp/recursion_input.bin"; + std::fs::write(path, &blob[..]).expect("write blob"); + eprintln!("[dump-input] wrote {} bytes to {path}", blob.len()); +} + +/// Cycle count only of the recursion guest verifying a 1-query inner proof. +#[test] +#[ignore = "diagnostic: fast; recursion guest cycle count (1 query)"] +fn test_recursion_cycles_1query() { + run_profile("recursion", 500, MIN_PROOF_OPTIONS, false); +} + +/// Cycle count only at 128-bit security: more FRI queries → more verifier cycles. +#[test] +#[ignore = "diagnostic: fast; recursion guest cycle count (multi-query)"] +fn test_recursion_cycles_multiquery() { + run_profile("recursion", 500, blowup8(), false); +} + +/// Full profile (top-25 + per-step) of the 1-query run. +#[test] +#[ignore = "diagnostic: ~8 min; recursion guest histogram + steps (1 query)"] +fn test_recursion_profile_1query() { + run_profile("recursion", 500, MIN_PROOF_OPTIONS, true); +} + +/// Full profile at 128-bit security: weight shifts toward per-query FRI/Merkle. +#[test] +#[ignore = "diagnostic: heavy; recursion guest histogram + steps (multi-query)"] +fn test_recursion_profile_multiquery() { + run_profile("recursion", 500, blowup8(), true); +} + /// Inner program: fibonacci(10). #[test] #[ignore = "slow: memory-bounded continuation prove of the verifier-in-VM"] diff --git a/prover/src/tests/statement_tests.rs b/prover/src/tests/statement_tests.rs index 73944e262..df8236666 100644 --- a/prover/src/tests/statement_tests.rs +++ b/prover/src/tests/statement_tests.rs @@ -39,7 +39,8 @@ fn sample_ranges() -> Vec { ] } -fn state_after_absorb( +fn state_after_absorb_with_digest( + vk_digest: [u8; 32], elf: &[u8], out: &[u8], counts: &TableCounts, @@ -49,7 +50,7 @@ fn state_after_absorb( let mut t = DefaultTranscript::::new(&[]); absorb_statement( &mut t, - StatementKind::Monolithic, + StatementKind::Monolithic { vk_digest }, elf, out, counts, @@ -59,6 +60,16 @@ fn state_after_absorb( t.state() } +fn state_after_absorb( + elf: &[u8], + out: &[u8], + counts: &TableCounts, + priv_pages: usize, + ranges: &[RuntimePageRange], +) -> [u8; 32] { + state_after_absorb_with_digest([7u8; 32], elf, out, counts, priv_pages, ranges) +} + #[test] fn state_is_deterministic() { let a = state_after_absorb(b"elf", b"out", &sample_counts(), 3, &sample_ranges()); @@ -112,6 +123,19 @@ fn state_depends_on_every_field() { state_after_absorb(b"elf", b"out", &sample_counts(), 1, &[]), "state must depend on runtime_page_ranges", ); + + assert_ne!( + baseline, + state_after_absorb_with_digest( + [8u8; 32], + b"elf", + b"out", + &sample_counts(), + 1, + &sample_ranges() + ), + "state must depend on vk_digest", + ); } #[test] diff --git a/prover/src/tests/vkey_tests.rs b/prover/src/tests/vkey_tests.rs new file mode 100644 index 000000000..932ef9058 --- /dev/null +++ b/prover/src/tests/vkey_tests.rs @@ -0,0 +1,182 @@ +//! Tests for [`crate::VmVerifyingKey`] and the vkey-aware verify path. + +use executor::elf::Elf; +use stark::proof::options::{GoldilocksCubicProofOptions, ProofOptions}; + +use crate::VmVerifyingKey; +use crate::tables::page::PageConfig; +use crate::tables::trace_builder::Traces; +use crate::test_utils::asm_elf_bytes; +use crate::vkey::VKEY_VERSION; +use crate::{Error, VmProof, prove}; + +fn default_options() -> ProofOptions { + GoldilocksCubicProofOptions::with_blowup(2).expect("blowup=2 is always valid") +} + +/// Derive the same `page_configs` slice the verifier would reconstruct from +/// `vm_proof`. This is exactly what `verify_with_options_with_vkey` does +/// internally, lifted into the test so the test-side and verifier-side +/// `vkey.pages` indexing line up. +fn page_configs_from_proof(elf: &Elf, vm_proof: &VmProof) -> Vec { + Traces::page_configs_from_elf_and_runtime( + elf, + &vm_proof.runtime_page_ranges, + vm_proof.num_private_input_pages, + ) +} + +/// Prove `sub`, and derive the honest vkey the same way the verifier would. +fn proof_and_vkey() -> (Vec, VmProof, ProofOptions, VmVerifyingKey) { + let elf_bytes = asm_elf_bytes("sub"); + let vm_proof = prove(&elf_bytes).expect("inner prove should succeed"); + let elf = Elf::load(&elf_bytes).expect("ELF load failed"); + let options = default_options(); + let page_configs = page_configs_from_proof(&elf, &vm_proof); + let vkey = VmVerifyingKey::from_elf_and_options(&elf, &options, None, &page_configs); + (elf_bytes, vm_proof, options, vkey) +} + +/// A tampered or malformed vkey must be rejected with an explicit +/// `InvalidVerifyingKey` before any STARK work runs — either by the shape +/// checks or by the `vk_digest` comparison against the proof. +fn assert_rejects_vkey( + elf_bytes: &[u8], + vm_proof: &VmProof, + options: &ProofOptions, + vkey: &VmVerifyingKey, + what: &str, +) { + let result = + crate::verify_with_options_with_vkey(vm_proof, elf_bytes, options, None, None, Some(vkey)); + assert!( + matches!(result, Err(Error::InvalidVerifyingKey(_))), + "{what} must be rejected with InvalidVerifyingKey, got {result:?}" + ); +} + +#[test] +fn test_vkey_roundtrip() { + let (_, vm_proof, options, vkey) = proof_and_vkey(); + let elf_bytes = asm_elf_bytes("sub"); + let elf = Elf::load(&elf_bytes).expect("ELF load failed"); + let page_configs = page_configs_from_proof(&elf, &vm_proof); + + assert_eq!(vkey.version, VKEY_VERSION, "version field must be set"); + assert_eq!(vkey.options, options, "options must be embedded"); + assert_eq!( + vkey.pages.len(), + page_configs.len(), + "vkey.pages must have one entry per page config", + ); + let digest_before = vkey.compute_digest(); + assert_eq!( + vm_proof.vk_digest, digest_before, + "prover must stamp the same digest the verifier derives" + ); + + // Two host derivations on the same inputs must produce the same vkey; + // the per-table commitment caches should not change between calls. + let vkey_again = VmVerifyingKey::from_elf_and_options(&elf, &options, None, &page_configs); + assert_eq!(vkey, vkey_again, "vkey derivation must be deterministic"); + + // rkyv round-trip preserves every field. + let encoded = rkyv::to_bytes::(&vkey).expect("rkyv encode"); + let decoded: VmVerifyingKey = + rkyv::from_bytes::<_, rkyv::rancor::Error>(&encoded).expect("rkyv decode"); + assert_eq!(vkey, decoded, "rkyv round-trip must preserve the vkey"); + assert_eq!( + decoded.compute_digest(), + digest_before, + "digest must be stable across serialization" + ); +} + +#[test] +fn test_vkey_verify_equivalence() { + // Prove a tiny program once with the full (non-minimal) bitwise table, + // then verify it both ways: with and without a precomputed vkey. + // Both paths must accept the proof. This is the core correctness + // guarantee — the vkey shortcut produces identical results to the + // recompute-from-scratch path. + let (elf_bytes, vm_proof, options, vkey) = proof_and_vkey(); + + let baseline = crate::verify_with_options(&vm_proof, &elf_bytes, &options, None, None) + .expect("baseline verify errored"); + assert!(baseline, "baseline verify must accept the proof"); + + let with_vkey = crate::verify_with_options_with_vkey( + &vm_proof, + &elf_bytes, + &options, + None, + None, + Some(&vkey), + ) + .expect("vkey verify errored"); + assert!(with_vkey, "vkey verify must accept the same proof"); +} + +#[test] +fn test_vkey_mismatch_rejects() { + let (elf_bytes, vm_proof, options, mut vkey) = proof_and_vkey(); + vkey.bitwise[0] ^= 0xFF; + assert_rejects_vkey(&elf_bytes, &vm_proof, &options, &vkey, "tampered bitwise"); +} + +#[test] +fn test_vkey_page_mismatch_rejects() { + let (elf_bytes, vm_proof, options, mut vkey) = proof_and_vkey(); + let elf = Elf::load(&elf_bytes).expect("ELF load failed"); + let target = page_configs_from_proof(&elf, &vm_proof) + .iter() + .position(|c| !c.is_private_input) + .expect("test ELF must produce at least one non-private-input page"); + vkey.pages[target][0] ^= 0xFF; + assert_rejects_vkey(&elf_bytes, &vm_proof, &options, &vkey, "tampered page"); +} + +#[test] +fn test_vkey_decode_mismatch_rejects() { + let (elf_bytes, vm_proof, options, mut vkey) = proof_and_vkey(); + vkey.decode[0] ^= 0xFF; + assert_rejects_vkey(&elf_bytes, &vm_proof, &options, &vkey, "tampered decode"); +} + +#[test] +fn test_vkey_register_mismatch_rejects() { + let (elf_bytes, vm_proof, options, mut vkey) = proof_and_vkey(); + vkey.register[0] ^= 0xFF; + assert_rejects_vkey(&elf_bytes, &vm_proof, &options, &vkey, "tampered register"); +} + +#[test] +fn test_vkey_keccak_rc_mismatch_rejects() { + let (elf_bytes, vm_proof, options, mut vkey) = proof_and_vkey(); + vkey.keccak_rc[0] ^= 0xFF; + assert_rejects_vkey(&elf_bytes, &vm_proof, &options, &vkey, "tampered keccak_rc"); +} + +#[test] +fn test_vkey_short_pages_rejects() { + // A short pages vec must be a clean error, not an out-of-bounds panic. + let (elf_bytes, vm_proof, options, mut vkey) = proof_and_vkey(); + vkey.pages.clear(); + assert_rejects_vkey(&elf_bytes, &vm_proof, &options, &vkey, "short pages vec"); +} + +#[test] +fn test_vkey_options_mismatch_rejects() { + // Query count and grinding factor affect soundness but no commitment, + // so a weakened-options vkey must be caught by the explicit check. + let (elf_bytes, vm_proof, options, mut vkey) = proof_and_vkey(); + vkey.options.fri_number_of_queries = 1; + assert_rejects_vkey(&elf_bytes, &vm_proof, &options, &vkey, "weakened options"); +} + +#[test] +fn test_vkey_wrong_version_rejects() { + let (elf_bytes, vm_proof, options, mut vkey) = proof_and_vkey(); + vkey.version = VKEY_VERSION - 1; + assert_rejects_vkey(&elf_bytes, &vm_proof, &options, &vkey, "wrong version"); +} diff --git a/prover/src/vkey.rs b/prover/src/vkey.rs new file mode 100644 index 000000000..e07d9b74a --- /dev/null +++ b/prover/src/vkey.rs @@ -0,0 +1,177 @@ +//! Verifying key for the lambda-vm STARK verifier. +//! +//! Caches preprocessed-table Merkle commitments that the verifier would +//! otherwise recompute on every call. Mirrors the SP1 `MachineVerifyingKey` +//! pattern (preprocessed commitments derived once at setup, never recomputed +//! per-proof) and the prover-side companion in +//! (which caches the +//! same data on the prover side). +//! +//! ## Current scope +//! +//! All five preprocessed tables — BITWISE, DECODE, REGISTER, KECCAK_RC, and +//! every non-private-input PAGE — are cached here, together with the +//! [`ProofOptions`] the commitments were derived under. `VmAirs::new_with_vkey` +//! prefers the vkey-supplied commitment over recomputing when a vkey is +//! provided. The `version` field exists so a vkey serialized against an +//! older layout produces a different `compute_digest()` and stops +//! validating. +//! +//! ## Security +//! +//! The vkey is **trusted input**. Fiat-Shamir only detects a vkey that is +//! inconsistent with the proof (post-hoc tampering); a coordinated prover +//! can commit to a forged preprocessed table from the start and supply a +//! matching vkey, and the transcript stays self-consistent. The binding +//! that makes recursion sound is `compute_digest()`: +//! +//! - The prover stamps it into `VmProof::vk_digest` and binds it into the +//! Fiat-Shamir statement; the verifier recomputes it from its own vkey +//! and rejects on mismatch before any STARK work. +//! - The recursion guest commits `vk_digest ‖ inner public output`, so the +//! *outer* verifier can check which vkey was used in-guest against a +//! digest derived from the trusted inner ELF. Without that outer check +//! the guest's result says nothing — every guest input is prover-supplied. +//! +//! The digest covers the embedded [`ProofOptions`]: query count and +//! grinding factor affect soundness but no commitment, so nothing else +//! would pin them. + +use executor::elf::Elf; +use sha3::{Digest, Keccak256}; +use stark::config::Commitment; +use stark::proof::options::ProofOptions; + +use crate::tables::bitwise; +use crate::tables::decode; +use crate::tables::keccak_rc; +use crate::tables::page::{self, PageConfig}; +use crate::tables::register; + +/// Current `VmVerifyingKey` layout version. Bump whenever fields are added, +/// removed, or reordered so that vkeys serialized against an older layout +/// produce a different `compute_digest()` and stop validating. +pub const VKEY_VERSION: u32 = 4; + +/// Placeholder commitment stored in [`VmVerifyingKey::pages`] for +/// private-input page slots, where there is no preprocessed commitment to +/// cache. The verifier never reads these slots (private-input pages have no +/// `with_preprocessed(...)` call in `VmAirs::new`). +const PRIVATE_INPUT_PAGE_PLACEHOLDER: Commitment = [0u8; 32]; + +/// Cached preprocessed-table commitments the verifier would otherwise +/// recompute on every call. +#[derive(Debug, Clone, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +pub struct VmVerifyingKey { + /// Layout version. See [`VKEY_VERSION`]. + pub version: u32, + /// The options every commitment below was derived under. In the digest + /// because query count and grinding factor affect soundness but no + /// commitment. + pub options: ProofOptions, + /// Merkle root over the LDE of the bitwise preprocessed columns. + /// Program-independent; depends only on `ProofOptions`. + pub bitwise: Commitment, + /// Merkle root over the LDE of the decode preprocessed columns. + /// Program-dependent: derived from the inner ELF's instruction stream. + pub decode: Commitment, + /// Merkle root over the LDE of the register preprocessed columns. + /// Program-dependent via the ELF's entry point. + pub register: Commitment, + /// Merkle root over the LDE of the keccak round-constants preprocessed + /// columns. Program-independent; depends only on `ProofOptions`. + pub keccak_rc: Commitment, + /// Per-page preprocessed Merkle roots, indexed parallel to the + /// `page_configs` slice the verifier reconstructs from the proof via + /// [`crate::tables::trace_builder::Traces::page_configs_from_elf_and_runtime`]. + /// Private-input slots hold a zero placeholder and are never read by the + /// verifier — they exist only to keep the index aligned with + /// `page_configs`, which interleaves preprocessed and private-input pages. + /// Prover (`traces.page_configs`) and verifier + /// (`page_configs_from_elf_and_runtime`) must derive the same page order + /// or the digests diverge. + pub pages: Vec, +} + +impl VmVerifyingKey { + /// Derive the verifying key on the host. + /// + /// `elf` is read to derive the program-dependent commitments (DECODE + /// from the instruction stream, REGISTER from `elf.entry_point`). + /// + /// `page_configs` must match exactly what the verifier will reconstruct + /// from the proof — i.e. the output of + /// `Traces::page_configs_from_elf_and_runtime(elf, runtime_page_ranges, + /// num_private_input_pages)`. The host can call that helper with the + /// values it already has after producing the inner proof. + pub fn from_elf_and_options( + elf: &Elf, + options: &ProofOptions, + register_init: Option<&[u32]>, + page_configs: &[PageConfig], + ) -> Self { + let pages = page_configs + .iter() + .map(|config| { + if config.is_private_input { + PRIVATE_INPUT_PAGE_PLACEHOLDER + } else { + page::precomputed_commitment_cached(config, options) + } + }) + .collect(); + let register_init = register_init + .map(<[u32]>::to_vec) + .unwrap_or_else(|| register::register_init_from_entry_point(elf.entry_point)); + Self { + version: VKEY_VERSION, + options: options.clone(), + bitwise: bitwise::preprocessed_commitment(options), + decode: decode::commitment_from_elf(elf, options) + .expect("decode commitment must compute"), + register: register::preprocessed_commitment(options, ®ister_init), + keccak_rc: keccak_rc::preprocessed_commitment(options), + pages, + } + } + + /// Keccak256 fingerprint of a canonical, framework-free encoding of the + /// vkey: every field is absorbed fixed-width (integers as little-endian + /// u64/u8, commitments raw, `pages` length-prefixed), so the encoding is + /// injective and stable as long as the field layout (and [`VKEY_VERSION`]) + /// does not change. The exhaustive destructure makes any field added to + /// `VmVerifyingKey` a compile error here — the signal to extend the + /// absorption below and bump [`VKEY_VERSION`]. + pub fn compute_digest(&self) -> [u8; 32] { + let Self { + version, + options: + ProofOptions { + blowup_factor, + fri_number_of_queries, + coset_offset, + grinding_factor, + }, + bitwise, + decode, + register, + keccak_rc, + pages, + } = self; + let mut hasher = Keccak256::new(); + hasher.update(version.to_le_bytes()); + hasher.update([*blowup_factor]); + hasher.update((*fri_number_of_queries as u64).to_le_bytes()); + hasher.update(coset_offset.to_le_bytes()); + hasher.update([*grinding_factor]); + hasher.update(bitwise); + hasher.update(decode); + hasher.update(register); + hasher.update(keccak_rc); + hasher.update((pages.len() as u64).to_le_bytes()); + for page in pages { + hasher.update(page); + } + hasher.finalize().into() + } +} diff --git a/syscalls/src/ef_io.rs b/syscalls/src/ef_io.rs index dabf7818d..4e21fd84e 100644 --- a/syscalls/src/ef_io.rs +++ b/syscalls/src/ef_io.rs @@ -18,7 +18,7 @@ use core::arch::asm; #[cfg(target_arch = "riscv64")] -use crate::syscalls::{PRIVATE_INPUT_START, SyscallNumbers}; +use crate::syscalls::{PRIVATE_INPUT_PAYLOAD_OFFSET, PRIVATE_INPUT_START, SyscallNumbers}; /// EF IO: return a zero-copy pointer and size for the private input. /// @@ -36,7 +36,7 @@ pub unsafe extern "C" fn read_input(buf_ptr: *mut *const u8, buf_size: *mut usiz unsafe { let len_ptr = PRIVATE_INPUT_START as *const u32; let len = core::ptr::read_volatile(len_ptr) as usize; - *buf_ptr = (PRIVATE_INPUT_START + 4) as *const u8; + *buf_ptr = (PRIVATE_INPUT_START + PRIVATE_INPUT_PAYLOAD_OFFSET) as *const u8; *buf_size = len; } } diff --git a/syscalls/src/syscalls.rs b/syscalls/src/syscalls.rs index 491315ecb..3a5a1dfc5 100644 --- a/syscalls/src/syscalls.rs +++ b/syscalls/src/syscalls.rs @@ -8,6 +8,12 @@ use core::arch::asm; #[cfg(target_arch = "riscv64")] pub const PRIVATE_INPUT_START: usize = 0xFF000000; +/// Byte offset of the private-input payload from [`PRIVATE_INPUT_START`]: the +/// `[len: u32 LE][reserved: 12 bytes]` header. The payload base is 16-aligned +/// so structured (e.g. rkyv-archived) input can be read in place. +/// Must match `executor::vm::memory::PRIVATE_INPUT_PAYLOAD_OFFSET`. +pub const PRIVATE_INPUT_PAYLOAD_OFFSET: usize = 16; + #[cfg(target_arch = "riscv64")] pub enum SyscallNumbers { Print = 1, @@ -87,7 +93,7 @@ pub fn get_private_input() -> Vec { // executor). The data pointer and length are within the memory-mapped region. let len_ptr = PRIVATE_INPUT_START as *const u32; let len = unsafe { core::ptr::read_volatile(len_ptr) } as usize; - let data_ptr = (PRIVATE_INPUT_START + 4) as *const u8; + let data_ptr = (PRIVATE_INPUT_START + PRIVATE_INPUT_PAYLOAD_OFFSET) as *const u8; let slice = unsafe { core::slice::from_raw_parts(data_ptr, len) }; slice.to_vec() } @@ -97,6 +103,28 @@ pub fn get_private_input() -> Vec { unimplemented!("syscalls are only implemented for riscv64 targets"); } +/// Borrow the private input bytes in place from the memory-mapped region — +/// no copy, no allocation. Same layout as [`get_private_input`]; the returned +/// slice starts at `PRIVATE_INPUT_START + PRIVATE_INPUT_PAYLOAD_OFFSET` (a +/// 16-aligned address) and lives for the whole execution (the host never +/// remaps the region). +#[cfg(target_arch = "riscv64")] +pub fn get_private_input_slice() -> &'static [u8] { + // SAFETY: The host pre-loads private input at PRIVATE_INPUT_START before + // execution. The 4-byte LE length prefix is always valid (written by the + // executor). The data pointer and length are within the memory-mapped + // region, which stays mapped and unmodified for the whole execution. + let len_ptr = PRIVATE_INPUT_START as *const u32; + let len = unsafe { core::ptr::read_volatile(len_ptr) } as usize; + let data_ptr = (PRIVATE_INPUT_START + PRIVATE_INPUT_PAYLOAD_OFFSET) as *const u8; + unsafe { core::slice::from_raw_parts(data_ptr, len) } +} + +#[cfg(not(target_arch = "riscv64"))] +pub fn get_private_input_slice() -> &'static [u8] { + unimplemented!("syscalls are only implemented for riscv64 targets"); +} + #[cfg(target_arch = "riscv64")] pub fn sys_halt() -> ! { // NOTE: no print_string here — the Print ecall is unmatched on the Ecall bus diff --git a/tooling/ethrex-tests/Cargo.lock b/tooling/ethrex-tests/Cargo.lock new file mode 100644 index 000000000..7d24708ed --- /dev/null +++ b/tooling/ethrex-tests/Cargo.lock @@ -0,0 +1,2415 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" + +[[package]] +name = "ark-bn254" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-ec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +dependencies = [ + "ahash", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "educe", + "fnv", + "hashbrown 0.15.5", + "itertools 0.13.0", + "num-bigint", + "num-integer", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "arrayvec", + "digest", + "educe", + "itertools 0.13.0", + "num-bigint", + "num-traits", + "paste", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ark-poly" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" +dependencies = [ + "ahash", + "ark-ff", + "ark-serialize", + "ark-std", + "educe", + "fnv", + "hashbrown 0.15.5", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "arrayvec", + "digest", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitcoin-consensus-encoding" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2d6094e2a1ba3c93b5a596fe5a10d1a10c3c6e06785cde89f693a044c01aa40" +dependencies = [ + "bitcoin-internals", +] + +[[package]] +name = "bitcoin-internals" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a30a22d1f112dde8e16be7b45c63645dc165cef254f835b3e1e9553e485cfa64" +dependencies = [ + "hex-conservative 0.3.2", +] + +[[package]] +name = "bitcoin-io" +version = "0.1.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb5de036369d1ac59d3c1819ebc4d850f89466f5401c571a285b6ed564a4cb78" +dependencies = [ + "bitcoin-consensus-encoding", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bca4c7abb40c8817d77403c880988cfd484f23ab2365726afb2f798363e2c4a2" +dependencies = [ + "bitcoin-io", + "hex-conservative 0.2.2", +] + +[[package]] +name = "bitvec" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bls12_381" +version = "0.8.0" +source = "git+https://github.com/lambdaclass/bls12_381?branch=expose-affine-constructors#78cad0378b17fc3157b83f514be192bf46edf9a1" +dependencies = [ + "digest", + "ff", + "group", + "pairing", + "rand_core", + "subtle", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + +[[package]] +name = "bytecheck" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0caa33a2c0edca0419d15ac723dff03f1956f7978329b1e3b5fdaaaed9d3ca8b" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "rancor", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89385e82b5d1821d2219e0b095efa2cc1f246cbf99080f3be46a1a85c0d392d9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" +dependencies = [ + "serde", +] + +[[package]] +name = "cc" +version = "1.2.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const_format" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" +dependencies = [ + "const_format_proc_macros", + "konst", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ecsm" +version = "0.1.0" +dependencies = [ + "k256", + "num-bigint", + "num-traits", +] + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "enum-ordinalize" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07f808d588c10e464ea6f7d3eaed500049eff30aaac103460f61828c2d65b3eb" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e528e2d34ba8a67a1a650b86beae8ef69fc5fdb638016f386b973226590432" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "ethbloom" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c321610643004cf908ec0f5f2aa0d8f1f8e14b540562a2887a1111ff1ecbf7b" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ab15ed80916029f878e0267c3a9f92b67df55e79af370bf66199059ae2b4ee3" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + +[[package]] +name = "ethrex-common" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +dependencies = [ + "bytes", + "crc32fast", + "ethereum-types", + "ethrex-crypto", + "ethrex-rlp", + "ethrex-trie", + "hex", + "hex-literal", + "hex-simd", + "indexmap 2.14.0", + "lazy_static", + "libc", + "lru", + "once_cell", + "rayon", + "rkyv", + "rustc-hash", + "secp256k1", + "serde", + "serde_json", + "sha2", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "ethrex-crypto" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +dependencies = [ + "ark-bn254", + "ark-ec", + "ark-ff", + "bls12_381", + "ethereum-types", + "ff", + "hex-literal", + "k256", + "malachite", + "num-bigint", + "p256", + "ripemd", + "secp256k1", + "sha2", + "thiserror 2.0.18", + "tiny-keccak", +] + +[[package]] +name = "ethrex-guest-program" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +dependencies = [ + "bytes", + "ethereum-types", + "ethrex-common", + "ethrex-crypto", + "ethrex-l2-common", + "ethrex-rlp", + "ethrex-vm", + "hex", + "rkyv", + "serde", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "ethrex-l2-common" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +dependencies = [ + "bytes", + "ethereum-types", + "ethrex-common", + "ethrex-crypto", + "k256", + "lambdaworks-crypto", + "rkyv", + "secp256k1", + "serde", + "serde_with", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "ethrex-levm" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +dependencies = [ + "bytes", + "derive_more", + "ethrex-common", + "ethrex-crypto", + "ethrex-rlp", + "malachite", + "rayon", + "rustc-hash", + "serde", + "strum", + "thiserror 2.0.18", +] + +[[package]] +name = "ethrex-rlp" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +dependencies = [ + "bytes", + "ethereum-types", + "thiserror 2.0.18", +] + +[[package]] +name = "ethrex-tests" +version = "0.1.0" +dependencies = [ + "ethrex-guest-program", + "executor", + "rkyv", +] + +[[package]] +name = "ethrex-trie" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +dependencies = [ + "anyhow", + "bytes", + "crossbeam", + "ethereum-types", + "ethrex-crypto", + "ethrex-rlp", + "lazy_static", + "rayon", + "rkyv", + "rustc-hash", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "ethrex-vm" +version = "13.0.0" +source = "git+https://github.com/lambdaclass/ethrex.git?rev=156cb8d6a3974f411d71622eecd1b249ee37ff1c#156cb8d6a3974f411d71622eecd1b249ee37ff1c" +dependencies = [ + "bytes", + "derive_more", + "dyn-clone", + "ethrex-common", + "ethrex-crypto", + "ethrex-levm", + "ethrex-rlp", + "rayon", + "rustc-hash", + "serde", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "executor" +version = "0.1.0" +dependencies = [ + "ecsm", + "rustc-demangle", + "thiserror 1.0.69", +] + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "bitvec", + "rand_core", + "subtle", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex-conservative" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830e599c2904b08f0834ee6337d8fe8f0ed4a63b5d9e7a7f49c0ffa06d08d360" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "hex-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f7685beb53fc20efc2605f32f5d51e9ba18b8ef237961d1760169d2290d3bee" +dependencies = [ + "outref", + "vsimd", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "impl-codec" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d40b9d5e17727407e55028eafc22b2dc68781786e6d7eb8a21103f5058e3a14" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54ed8ad1f3877f7e775b8cbf30ed1bd3209a95401817f19a0eb4402d13f8cf90" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a143eada6a1ec4aefa5049037a26a6d597bfd64f8c026d07b77133e02b7dd0b" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "konst" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" +dependencies = [ + "konst_macro_rules", +] + +[[package]] +name = "konst_macro_rules" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" + +[[package]] +name = "lambdaworks-crypto" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58b1a1c1102a5a7fbbda117b79fb3a01e033459c738a3c1642269603484fd1c1" +dependencies = [ + "lambdaworks-math", + "rand", + "rand_chacha", + "serde", + "sha2", + "sha3", +] + +[[package]] +name = "lambdaworks-math" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "018a95aa873eb49896a858dee0d925c33f3978d073c64b08dd4f2c9b35a017c6" +dependencies = [ + "getrandom", + "num-bigint", + "num-traits", + "rand", + "serde", + "serde_json", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "lru" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39" +dependencies = [ + "hashbrown 0.16.1", +] + +[[package]] +name = "malachite" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec410515e231332b14cd986a475d1c3323bcfa4c7efc038bfa1d5b410b1c57e4" +dependencies = [ + "malachite-base", + "malachite-nz", + "malachite-q", +] + +[[package]] +name = "malachite-base" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c738d3789301e957a8f7519318fcbb1b92bb95863b28f6938ae5a05be6259f34" +dependencies = [ + "hashbrown 0.15.5", + "itertools 0.14.0", + "libm", + "ryu", +] + +[[package]] +name = "malachite-nz" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1707c9a1fa36ce21749b35972bfad17bbf34cf5a7c96897c0491da321e387d3b" +dependencies = [ + "itertools 0.14.0", + "libm", + "malachite-base", + "wide", +] + +[[package]] +name = "malachite-q" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d764801aa4e96bbb69b389dcd03b50075345131cd63ca2e380bca71cc37a3675" +dependencies = [ + "itertools 0.14.0", + "malachite-base", + "malachite-nz", +] + +[[package]] +name = "memchr" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" + +[[package]] +name = "munge" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e17401f259eba956ca16491461b6e8f72913a0a114e39736ce404410f915a0c" +dependencies = [ + "munge_macro", +] + +[[package]] +name = "munge_macro" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4568f25ccbd45ab5d5603dc34318c1ec56b117531781260002151b8530a9f931" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-bigint" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c863e9ab5e7bf9c99ba75e1050f1e4d624ae87ed3532d6238ffbdc7b585dbbe6" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "pairing" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" +dependencies = [ + "group", +] + +[[package]] +name = "parity-scale-codec" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "rustversion", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "primitive-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "ptr_meta" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9a0cf95a1196af61d4f1cbdab967179516d9a4a4312af1f31948f8f6224a79" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7347867d0a7e1208d93b46767be83e2b8f978c3dad35f775ac8d8847551d6fe1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rancor" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daff8b7b3ccf5f7ba270b3e7a0a4d4c701c5797e38dec27c7e2c3dbb830fed1c" +dependencies = [ + "ptr_meta", +] + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "rend" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "663ba70707f96e871406fe10d68128412e619b06d1d47cb91c3a4c6501176240" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest", +] + +[[package]] +name = "rkyv" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73389e0c99e664f919275ab5b5b0471391fe9a8de61e1dff9b1eaf56a90f16e3" +dependencies = [ + "bytecheck", + "bytes", + "hashbrown 0.17.1", + "indexmap 2.14.0", + "munge", + "ptr_meta", + "rancor", + "rend", + "rkyv_derive", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d2ed0b54125315fb36bd021e82d314d1c126548f871634b483f46b31d13cac6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "rlp" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa24e92bb2a83198bb76d661a71df9f7076b8c420b8696e4d3d97d50d94479e3" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" +dependencies = [ + "bitcoin_hashes", + "rand", + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_with" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" +dependencies = [ + "base64", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "uint" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "uuid" +version = "1.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +dependencies = [ + "bytemuck", + "safe_arch", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "zerocopy" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/tooling/ethrex-tests/Cargo.toml b/tooling/ethrex-tests/Cargo.toml new file mode 100644 index 000000000..f4774278d --- /dev/null +++ b/tooling/ethrex-tests/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "ethrex-tests" +version = "0.1.0" +edition = "2024" + +# Detached workspace: ethrex pins rkyv with the `unaligned` feature (a global +# archived-layout switch), which must never feature-unify with the main +# workspace's aligned rkyv proof format. See tests/ethrex.rs. +[workspace] + +[dev-dependencies] +executor = { path = "../../executor" } +# Pinned to the SAME ethrex rev as the guest (open LambdaVM-backend PR branch) +# so the native reference reads the same ProgramInput rkyv layout. +ethrex-guest-program = { git = "https://github.com/lambdaclass/ethrex.git", rev = "156cb8d6a3974f411d71622eecd1b249ee37ff1c", package = "ethrex-guest-program" } +# Exact pin: the fixture writer and the guest/executor readers must agree on the +# rkyv layout. Keep this in sync with tooling/ethrex-fixtures and +# executor/programs/rust/ethrex/Cargo.toml. +rkyv = { version = "=0.8.16", features = ["std", "unaligned"] } + +# Match the root workspace's optimized dev profile: these tests execute the VM. +[profile.dev] +opt-level = 3 +debug = true diff --git a/tooling/ethrex-tests/tests/ethrex.rs b/tooling/ethrex-tests/tests/ethrex.rs new file mode 100644 index 000000000..c87ccceba --- /dev/null +++ b/tooling/ethrex-tests/tests/ethrex.rs @@ -0,0 +1,88 @@ +//! Host-reference execution tests for the ethrex guest, relocated out of the +//! `executor` test suite: `ethrex-guest-program` pins rkyv with the +//! `unaligned` feature (a global archived-layout switch), which would +//! feature-unify with the main workspace's aligned rkyv and silently change +//! the proof wire format. This crate is a detached workspace so the two rkyv +//! configurations never meet. +//! +//! Fixtures are generated by `tooling/ethrex-fixtures`; the guest ELF comes +//! from `make compile-programs`. See `executor/tests/README.md`. + +use executor::elf::Elf; +use executor::vm::execution::Executor; +use executor::vm::execution::ReturnValues; + +fn run_program_without_expect( + elf_path: &str, + private_inputs: Vec, +) -> Result { + println!("Testing {}", elf_path); + let elf_data = std::fs::read(elf_path).unwrap(); + let program = Elf::load(&elf_data).unwrap(); + println!("Program entry: 0x{:016x}", program.entry_point); + let mut executor = Executor::new(&program, private_inputs)?; + while let Some(_logs) = executor.resume()? {} + executor.finish() +} + +fn run_program_and_check_public_output( + elf_path: &str, + expected_output: Vec, + private_inputs: Vec, +) { + let result = + run_program_without_expect(elf_path, private_inputs).expect("Failed to run program"); + + assert_eq!(result.memory_values, expected_output); +} + +const ELF_PATH: &str = "../../executor/program_artifacts/rust/ethrex.elf"; +const FIXTURES_DIR: &str = "../../executor/tests"; + +/// Larger-block smoke test: a synthetic ethrex block with 10 ETH transfers. +/// (Replaces the old `ethrex_hoodi.bin` real-block fixture, which was in the +/// pre-Crypto-trait ethrex format and no longer deserializes.) +#[ignore = "heavier synthetic block (10 txs); run in the dedicated --ignored CI step"] +#[test] +fn test_ethrex() { + use ethrex_guest_program::crypto::NativeCrypto; + use ethrex_guest_program::l1::{ProgramInput, execution_program}; + use rkyv::rancor::Error; + use std::fs; + use std::sync::Arc; + let inputs = fs::read(format!("{FIXTURES_DIR}/ethrex_10_transfers.bin")).unwrap(); + let input = rkyv::from_bytes::(&inputs).unwrap(); + let output = execution_program(input, Arc::new(NativeCrypto)).unwrap(); + run_program_and_check_public_output(ELF_PATH, output.encode(), inputs); +} + +/// Executes a stateless ethrex block containing a single (plain ETH transfer) +/// transaction. Execution only — no proving — against the ethrex guest ELF +/// built from the same pinned ethrex revision as the native reference. +#[test] +fn test_ethrex_simple_tx() { + use ethrex_guest_program::crypto::NativeCrypto; + use ethrex_guest_program::l1::{ProgramInput, execution_program}; + use rkyv::rancor::Error; + use std::sync::Arc; + let inputs = std::fs::read(format!("{FIXTURES_DIR}/ethrex_simple_tx.bin")).unwrap(); + let input = rkyv::from_bytes::(&inputs).unwrap(); + let output = execution_program(input, Arc::new(NativeCrypto)).unwrap(); + run_program_and_check_public_output(ELF_PATH, output.encode(), inputs); +} + +/// Executes a stateless ethrex block with NO transactions (empty block). +/// Execution only — no proving. Pins the committed `ethrex_empty_block.bin` +/// fixture so its rkyv `ProgramInput` layout (the 0-tx edge case) is +/// exercised and stays consistent with the guest across ethrex rev bumps. +#[test] +fn test_ethrex_empty_block() { + use ethrex_guest_program::crypto::NativeCrypto; + use ethrex_guest_program::l1::{ProgramInput, execution_program}; + use rkyv::rancor::Error; + use std::sync::Arc; + let inputs = std::fs::read(format!("{FIXTURES_DIR}/ethrex_empty_block.bin")).unwrap(); + let input = rkyv::from_bytes::(&inputs).unwrap(); + let output = execution_program(input, Arc::new(NativeCrypto)).unwrap(); + run_program_and_check_public_output(ELF_PATH, output.encode(), inputs); +}