diff --git a/.github/scripts/merge_mobench_split_runs.py b/.github/scripts/merge_mobench_split_runs.py new file mode 100644 index 000000000..9cdcbf839 --- /dev/null +++ b/.github/scripts/merge_mobench_split_runs.py @@ -0,0 +1,290 @@ +#!/usr/bin/env python3 +"""Merge one-sample mobench CI summaries into a normal per-device summary.""" + +from __future__ import annotations + +import argparse +import copy +import csv +import json +import math +from datetime import datetime, timezone +from pathlib import Path +from statistics import median +from typing import Any + + +def percentile(values: list[int], pct: float) -> int: + if not values: + return 0 + ordered = sorted(values) + index = max(0, min(len(ordered) - 1, math.ceil((pct / 100.0) * len(ordered)) - 1)) + return ordered[index] + + +def int_median(values: list[int]) -> int: + if not values: + return 0 + return int(median(values)) + + +def load_reports(samples_dir: Path) -> list[tuple[Path, dict[str, Any]]]: + reports = [] + for summary_path in sorted(samples_dir.glob("sample-*/summary.json")): + with summary_path.open() as file: + reports.append((summary_path, json.load(file))) + if not reports: + raise SystemExit(f"no sample summary.json files found under {samples_dir}") + return reports + + +def single_benchmark(report: dict[str, Any]) -> tuple[str, dict[str, Any]]: + benchmark_results = report.get("benchmark_results") or {} + if len(benchmark_results) != 1: + raise ValueError("expected exactly one device in benchmark_results") + device, benchmarks = next(iter(benchmark_results.items())) + if len(benchmarks) != 1: + raise ValueError("expected exactly one benchmark in benchmark_results") + return device, benchmarks[0] + + +def merge_resources(samples: list[dict[str, Any]], benches: list[dict[str, Any]]) -> dict[str, Any]: + resources = copy.deepcopy(benches[0].get("resources") or {}) + cpu_samples = [sample.get("cpu_time_ms") for sample in samples if sample.get("cpu_time_ms") is not None] + peak_memory = [ + sample.get("peak_memory_kb") for sample in samples if sample.get("peak_memory_kb") is not None + ] + process_peak = [ + sample.get("process_peak_memory_kb") + for sample in samples + if sample.get("process_peak_memory_kb") is not None + ] + + if cpu_samples: + resources["cpu_total_ms"] = int(sum(cpu_samples)) + resources["elapsed_cpu_ms"] = int(sum(cpu_samples)) + resources["cpu_median_ms"] = int_median([int(value) for value in cpu_samples]) + if peak_memory: + resources["peak_memory_kb"] = int(max(peak_memory)) + resources["peak_memory_growth_kb"] = int(max(peak_memory)) + if process_peak: + resources["process_peak_memory_kb"] = int(max(process_peak)) + + resources.setdefault("platform", "android") + resources.setdefault("memory_process", "isolated_worker") + return resources + + +def merge_reports( + reports: list[tuple[Path, dict[str, Any]]], + function: str, + iterations: int, + warmup: int, +) -> dict[str, Any]: + device_names = [] + benches = [] + for _, report in reports: + device, benchmark = single_benchmark(report) + device_names.append(device) + benches.append(benchmark) + + if len(set(device_names)) != 1: + raise ValueError(f"split samples reported multiple devices: {sorted(set(device_names))}") + if any(benchmark.get("function") != function for benchmark in benches): + functions = sorted({benchmark.get("function") for benchmark in benches}) + raise ValueError(f"split samples reported unexpected functions: {functions}") + + device = device_names[0] + base = copy.deepcopy(reports[0][1]) + samples: list[dict[str, Any]] = [] + for benchmark in benches: + samples.extend(copy.deepcopy(benchmark.get("samples") or [])) + + if len(samples) != iterations: + raise ValueError(f"expected {iterations} measured samples, got {len(samples)}") + + sample_ns = [int(sample["duration_ns"]) for sample in samples] + mean_ns = int(sum(sample_ns) / len(sample_ns)) + median_ns = int_median(sample_ns) + min_ns = min(sample_ns) + max_ns = max(sample_ns) + p95_ns = percentile(sample_ns, 95.0) + resources = merge_resources(samples, benches) + + merged_benchmark = copy.deepcopy(benches[0]) + merged_benchmark.update( + { + "function": function, + "samples": samples, + "samples_ns": sample_ns, + "min_ns": min_ns, + "max_ns": max_ns, + "mean_ns": mean_ns, + "median_ns": median_ns, + "p95_ns": p95_ns, + "resources": resources, + "phases": [{"name": "prove", "duration_ns": int(sum(sample_ns))}], + "spec": { + **(copy.deepcopy(merged_benchmark.get("spec") or {})), + "name": function, + "iterations": iterations, + "warmup": warmup, + }, + "stats": { + "avg_ns": mean_ns, + "mean_ns": mean_ns, + "median_ns": median_ns, + "min_ns": min_ns, + "max_ns": max_ns, + }, + } + ) + + summary_benchmark = { + "function": function, + "samples": len(samples), + "mean_ns": mean_ns, + "median_ns": median_ns, + "p95_ns": p95_ns, + "min_ns": min_ns, + "max_ns": max_ns, + "resource_usage": resources, + } + + base["benchmark_results"] = {device: [merged_benchmark]} + base["summary"] = { + **(copy.deepcopy(base.get("summary") or {})), + "target": "android", + "device_summaries": [{"device": device, "benchmarks": [summary_benchmark]}], + } + base["spec"] = { + **(copy.deepcopy(base.get("spec") or {})), + "name": function, + "iterations": iterations, + "warmup": warmup, + } + base.setdefault("ci", {})["split_android_samples"] = True + base["ci"]["split_sample_count"] = iterations + return base + + +def human_duration(ns: int) -> str: + seconds = ns / 1_000_000_000.0 + if seconds >= 1: + return f"{seconds:.3f}s" + return f"{seconds * 1000:.1f}ms" + + +def human_memory(kb: int | None) -> str: + if not kb: + return "-" + mb = kb / 1024.0 + if mb >= 1024: + return f"{mb / 1024.0:.2f} GB" + return f"{mb:.2f} MB" + + +def write_csv(output_dir: Path, device: str, benchmark: dict[str, Any]) -> None: + resources = benchmark.get("resource_usage") or benchmark.get("resources") or {} + fieldnames = [ + "device", + "function", + "samples", + "mean_ns", + "median_ns", + "p95_ns", + "min_ns", + "max_ns", + "cpu_total_ms", + "cpu_median_ms", + "peak_memory_kb", + "peak_memory_growth_kb", + "process_peak_memory_kb", + ] + with (output_dir / "results.csv").open("w", newline="") as file: + writer = csv.DictWriter(file, fieldnames=fieldnames) + writer.writeheader() + writer.writerow( + { + "device": device, + "function": benchmark["function"], + "samples": benchmark["samples"], + "mean_ns": benchmark["mean_ns"], + "median_ns": benchmark["median_ns"], + "p95_ns": benchmark["p95_ns"], + "min_ns": benchmark["min_ns"], + "max_ns": benchmark["max_ns"], + "cpu_total_ms": resources.get("cpu_total_ms", ""), + "cpu_median_ms": resources.get("cpu_median_ms", ""), + "peak_memory_kb": resources.get("peak_memory_kb", ""), + "peak_memory_growth_kb": resources.get("peak_memory_growth_kb", ""), + "process_peak_memory_kb": resources.get("process_peak_memory_kb", ""), + } + ) + + +def write_markdown(output_dir: Path, device_arg: str, device: str, benchmark: dict[str, Any], warmup: int) -> None: + resources = benchmark.get("resource_usage") or benchmark.get("resources") or {} + mean_ns = int(benchmark["mean_ns"]) + samples = int(benchmark["samples"]) + cpu_total = resources.get("cpu_total_ms") + cpu_median = resources.get("cpu_median_ms") + peak_growth = resources.get("peak_memory_growth_kb") + process_peak = resources.get("process_peak_memory_kb") + wall_total_ns = mean_ns * samples + cpu_wall = "-" + if cpu_total is not None and wall_total_ns: + cpu_wall = f"{(cpu_total / (wall_total_ns / 1_000_000.0)) * 100:.1f}%" + + generated = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") + lines = [ + "### Benchmark Summary", + "", + f"- Generated: {generated}", + "- Target: Android", + f"- Function: {benchmark['function']}", + f"- Iterations/Warmup: {samples} / {warmup}", + f"- Devices: {device_arg}", + "", + "| Device | Function | Samples | Warmup | Wall mean / iter | Wall total | CPU median / iter | CPU total | CPU / wall | Peak growth | Process peak |", + "| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |", + ( + f"| {device} | {benchmark['function']} | {samples} | {warmup} | " + f"{human_duration(mean_ns)} | {human_duration(wall_total_ns)} | " + f"{human_duration(int(cpu_median) * 1_000_000) if cpu_median is not None else '-'} | " + f"{human_duration(int(cpu_total) * 1_000_000) if cpu_total is not None else '-'} | " + f"{cpu_wall} | {human_memory(peak_growth)} | {human_memory(process_peak)} |" + ), + "", + ] + (output_dir / "summary.md").write_text("\n".join(lines)) + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--samples-dir", type=Path, required=True) + parser.add_argument("--output-dir", type=Path, required=True) + parser.add_argument("--function", required=True) + parser.add_argument("--device", required=True) + parser.add_argument("--iterations", type=int, required=True) + parser.add_argument("--warmup", type=int, required=True) + args = parser.parse_args() + + reports = load_reports(args.samples_dir) + merged = merge_reports(reports, args.function, args.iterations, args.warmup) + args.output_dir.mkdir(parents=True, exist_ok=True) + + with (args.output_dir / "summary.json").open("w") as file: + json.dump(merged, file, indent=2) + file.write("\n") + + device, benchmark = single_benchmark(merged) + summary_benchmark = merged["summary"]["device_summaries"][0]["benchmarks"][0] + write_csv(args.output_dir, device, summary_benchmark) + write_markdown(args.output_dir, args.device, device, summary_benchmark, args.warmup) + + print(f"Merged {args.iterations} split sample(s) for {args.function} on {args.device}") + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9e6d2da47..7fd5535a5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,12 @@ jobs: channel: nightly-2026-03-04 cache-base: main components: rustfmt, clippy + - name: Setup Noir + uses: noir-lang/noirup@v0.1.2 + with: + toolchain: v1.0.0-beta.11 + - name: Generate mobile benchmark Noir artifacts + run: bench-mobile/scripts/generate-fixtures.sh - run: cargo fmt --all --check - run: cargo clippy --all-targets --all-features --verbose - run: cargo build --all-targets --all-features --verbose diff --git a/.github/workflows/mobile-bench-pr-auto.yml b/.github/workflows/mobile-bench-pr-auto.yml new file mode 100644 index 000000000..0f0cdd5de --- /dev/null +++ b/.github/workflows/mobile-bench-pr-auto.yml @@ -0,0 +1,150 @@ +name: Mobile Bench PR Auto + +on: + push: + branches: + - dcbuild3r/mobench-v1-browserstack + pull_request: + types: [labeled] + workflow_run: + workflows: ["Cargo Build & Test"] + types: [completed] + +permissions: + contents: write + actions: write + pull-requests: write + issues: write + checks: read + +jobs: + resolve: + name: Check compile gate and resolve context + runs-on: ubuntu-latest + if: >- + github.event_name == 'push' || + (github.event_name == 'pull_request' && github.event.action == 'labeled') || + (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') + outputs: + should_run: ${{ steps.pr.outputs.should_run }} + pr_number: ${{ steps.pr.outputs.pr_number }} + head_sha: ${{ steps.pr.outputs.head_sha }} + requested_by: ${{ steps.pr.outputs.requested_by }} + platform: ${{ steps.pr.outputs.platform }} + iterations: ${{ steps.pr.outputs.iterations }} + warmup: ${{ steps.pr.outputs.warmup }} + steps: + - name: Resolve PR context + id: pr + env: + GH_TOKEN: ${{ github.token }} + EVENT_NAME: ${{ github.event_name }} + PR_NUMBER_EVENT: ${{ github.event.pull_request.number }} + HEAD_SHA_PR: ${{ github.event.pull_request.head.sha }} + BASE_REF_PR: ${{ github.event.pull_request.base.ref }} + HEAD_SHA_WR: ${{ github.event.workflow_run.head_sha }} + HEAD_SHA_PUSH: ${{ github.sha }} + HEAD_COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + REPO: ${{ github.repository }} + run: | + set -euo pipefail + ITERATIONS="2" + WARMUP="1" + + if [ "$EVENT_NAME" = "pull_request" ]; then + PR_NUMBER="$PR_NUMBER_EVENT" + HEAD_SHA="$HEAD_SHA_PR" + REQUESTED_BY="auto:pull_request" + PLATFORM="both" + elif [ "$EVENT_NAME" = "workflow_run" ]; then + pr_json=$(gh api "repos/${REPO}/pulls?state=open&sort=updated&direction=desc&per_page=50" \ + --jq ".[] | select(.head.sha == \"${HEAD_SHA_WR}\") | {number, base_ref: .base.ref}" \ + | head -1) + if [ -z "$pr_json" ]; then + echo "::notice::No open PR found for SHA ${HEAD_SHA_WR}, skipping" + echo "should_run=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + PR_NUMBER=$(jq -r '.number' <<<"$pr_json") + HEAD_SHA="$HEAD_SHA_WR" + REQUESTED_BY="auto:workflow_run" + PLATFORM="both" + else + case "$HEAD_COMMIT_MESSAGE" in + *"[mobench:android:100]"*) PLATFORM="android"; ITERATIONS="100"; WARMUP="10" ;; + *"[mobench:ios:100]"*) PLATFORM="ios"; ITERATIONS="100"; WARMUP="10" ;; + *"[mobench:both:100]"*|*"[mobench:100]"*) PLATFORM="both"; ITERATIONS="100"; WARMUP="10" ;; + *"[mobench:android]"*) PLATFORM="android" ;; + *"[mobench:ios]"*) PLATFORM="ios" ;; + *"[mobench:both]"*) PLATFORM="both" ;; + *) + echo "::notice::Push does not include a mobench platform marker, skipping" + echo "should_run=false" >> "$GITHUB_OUTPUT" + exit 0 + ;; + esac + pr_json=$(gh api "repos/${REPO}/pulls?state=open&sort=updated&direction=desc&per_page=50" \ + --jq ".[] | select(.head.sha == \"${HEAD_SHA_PUSH}\") | {number, base_ref: .base.ref}" \ + | head -1) + if [ -z "$pr_json" ]; then + echo "::notice::No open PR found for SHA ${HEAD_SHA_PUSH}, skipping" + echo "should_run=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + PR_NUMBER=$(jq -r '.number' <<<"$pr_json") + HEAD_SHA="$HEAD_SHA_PUSH" + REQUESTED_BY="auto:push" + fi + + has_label=$(gh api "repos/${REPO}/issues/${PR_NUMBER}/labels" \ + --jq '.[].name' | grep -qx 'bench' && echo "true" || echo "false") + if [ "$has_label" != "true" ]; then + echo "::notice::PR #${PR_NUMBER} does not have 'bench' label, skipping" + echo "should_run=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + if [ "$EVENT_NAME" = "workflow_run" ] || [ "$EVENT_NAME" = "pull_request" ] || [ "$EVENT_NAME" = "push" ]; then + gate_status="success" + else + gate_status=$(gh api "repos/${REPO}/commits/${HEAD_SHA}/check-runs" \ + --jq '.check_runs[] | select((.name == "Build & Test (all features)" or .name == "Build and test" or .name == "Cargo Build & Test") and .conclusion == "success") | .conclusion' \ + | head -1) + fi + if [ "$gate_status" != "success" ]; then + echo "::notice::Compile gate 'Cargo Build & Test' not yet passed for ${HEAD_SHA} (status: ${gate_status:-pending})" + echo "should_run=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "pr_number=${PR_NUMBER}" >> "$GITHUB_OUTPUT" + echo "head_sha=${HEAD_SHA}" >> "$GITHUB_OUTPUT" + echo "requested_by=${REQUESTED_BY}" >> "$GITHUB_OUTPUT" + echo "platform=${PLATFORM}" >> "$GITHUB_OUTPUT" + echo "iterations=${ITERATIONS}" >> "$GITHUB_OUTPUT" + echo "warmup=${WARMUP}" >> "$GITHUB_OUTPUT" + echo "should_run=true" >> "$GITHUB_OUTPUT" + + browserstack: + name: Run BrowserStack benchmarks + needs: resolve + if: needs.resolve.outputs.should_run == 'true' + uses: ./.github/workflows/mobile-bench-reusable.yml + secrets: inherit + with: + crate_path: ./bench-mobile + functions: '["bench_mobile::bench_passport_complete_age_check_prove","bench_mobile::bench_passport_fragmented_age_check_prove","bench_mobile::bench_oprf_prove"]' + functions_ios: '["bench_mobile::bench_passport_complete_age_check_prove","bench_mobile::bench_passport_fragmented_age_check_prove","bench_mobile::bench_oprf_prove"]' + functions_android: '["bench_mobile::bench_oprf_prove","bench_mobile::bench_passport_fragmented_age_check_prove","bench_mobile::bench_passport_complete_age_check_prove"]' + platform: ${{ needs.resolve.outputs.platform }} + device_profile: triad + device_profile_android: triad + iterations: ${{ needs.resolve.outputs.iterations }} + warmup: ${{ needs.resolve.outputs.warmup }} + mobench_version: "0.1.41" + mobench_ref: "8c3f002ae515afaa7440107f4e671f7067d276e3" + pr_number: ${{ needs.resolve.outputs.pr_number }} + requested_by: ${{ needs.resolve.outputs.requested_by }} + head_sha: ${{ needs.resolve.outputs.head_sha }} diff --git a/.github/workflows/mobile-bench-pr-command.yml b/.github/workflows/mobile-bench-pr-command.yml new file mode 100644 index 000000000..848347369 --- /dev/null +++ b/.github/workflows/mobile-bench-pr-command.yml @@ -0,0 +1,118 @@ +name: Mobile Bench PR Command + +on: + issue_comment: + types: [created] + +permissions: + contents: write + actions: write + pull-requests: write + issues: write + +jobs: + resolve: + name: Parse /mobench and resolve context + if: >- + github.event_name == 'issue_comment' && + github.event.action == 'created' && + github.event.issue.pull_request && + startsWith(github.event.comment.body, '/mobench') + runs-on: ubuntu-latest + outputs: + trusted: ${{ steps.trust.outputs.trusted }} + platform: ${{ steps.parse.outputs.platform }} + device_profile: ${{ steps.parse.outputs.device_profile }} + iterations: ${{ steps.parse.outputs.iterations }} + warmup: ${{ steps.parse.outputs.warmup }} + head_sha: ${{ steps.pr.outputs.head_sha }} + pr_number: ${{ github.event.issue.number }} + requested_by: ${{ github.event.comment.user.login }} + steps: + - name: Check trust + id: trust + env: + AUTHOR_ASSOCIATION: ${{ github.event.comment.author_association }} + run: | + if echo "OWNER,MEMBER,COLLABORATOR" | tr ',' '\n' | grep -qx "$AUTHOR_ASSOCIATION"; then + echo "trusted=true" >> "$GITHUB_OUTPUT" + else + echo "::warning::Untrusted author association: $AUTHOR_ASSOCIATION" + echo "trusted=false" >> "$GITHUB_OUTPUT" + fi + + - name: Parse command + if: steps.trust.outputs.trusted == 'true' + id: parse + env: + COMMENT_BODY: ${{ github.event.comment.body }} + run: | + set -euo pipefail + line=$(echo "$COMMENT_BODY" | head -1) + + extract_val() { + echo "$line" | sed -n "s/.*${1}=\([^ ]*\).*/\1/p" + } + + platform=$(extract_val platform) + device_profile=$(extract_val device_profile) + iterations=$(extract_val iterations) + warmup=$(extract_val warmup) + + case "${platform:-both}" in + android|ios|both) platform="${platform:-both}" ;; + *) echo "::warning::Invalid platform '${platform}', defaulting to 'both'"; platform="both" ;; + esac + + case "${device_profile:-triad}" in + smoke|triad|worst) device_profile="${device_profile:-triad}" ;; + *) echo "::warning::Invalid device_profile '${device_profile}', defaulting to 'triad'"; device_profile="triad" ;; + esac + + if ! [[ "${iterations:-2}" =~ ^[0-9]+$ ]]; then + echo "::warning::Invalid iterations '${iterations}', defaulting to '2'" + iterations="2" + else + iterations="${iterations:-2}" + fi + + if ! [[ "${warmup:-1}" =~ ^[0-9]+$ ]]; then + echo "::warning::Invalid warmup '${warmup}', defaulting to '1'" + warmup="1" + else + warmup="${warmup:-1}" + fi + + echo "platform=${platform}" >> "$GITHUB_OUTPUT" + echo "device_profile=${device_profile}" >> "$GITHUB_OUTPUT" + echo "iterations=${iterations}" >> "$GITHUB_OUTPUT" + echo "warmup=${warmup}" >> "$GITHUB_OUTPUT" + + - name: Resolve PR refs + if: steps.trust.outputs.trusted == 'true' + id: pr + env: + GH_TOKEN: ${{ github.token }} + PR_URL: ${{ github.event.issue.pull_request.url }} + run: | + head_sha=$(gh api "$PR_URL" --jq '.head.sha') + echo "head_sha=${head_sha}" >> "$GITHUB_OUTPUT" + + browserstack: + name: Run BrowserStack benchmarks + needs: resolve + if: needs.resolve.outputs.trusted == 'true' + uses: ./.github/workflows/mobile-bench-reusable.yml + secrets: inherit + with: + crate_path: ./bench-mobile + functions: '["bench_mobile::bench_passport_complete_age_check_prove","bench_mobile::bench_passport_fragmented_age_check_prove","bench_mobile::bench_oprf_prove"]' + functions_ios: '["bench_mobile::bench_passport_complete_age_check_prove","bench_mobile::bench_passport_fragmented_age_check_prove","bench_mobile::bench_oprf_prove"]' + functions_android: '["bench_mobile::bench_passport_fragmented_age_check_prove","bench_mobile::bench_oprf_prove","bench_mobile::bench_passport_complete_age_check_prove"]' + platform: ${{ needs.resolve.outputs.platform }} + device_profile: ${{ needs.resolve.outputs.device_profile }} + iterations: ${{ needs.resolve.outputs.iterations }} + warmup: ${{ needs.resolve.outputs.warmup }} + pr_number: ${{ needs.resolve.outputs.pr_number }} + requested_by: ${{ needs.resolve.outputs.requested_by }} + head_sha: ${{ needs.resolve.outputs.head_sha }} diff --git a/.github/workflows/mobile-bench-reusable.yml b/.github/workflows/mobile-bench-reusable.yml new file mode 100644 index 000000000..98e280ca1 --- /dev/null +++ b/.github/workflows/mobile-bench-reusable.yml @@ -0,0 +1,1607 @@ +name: Reusable Mobile Benchmark (BrowserStack) + +on: + workflow_call: + inputs: + crate_path: + description: "Path to the benchmark crate in the caller repo" + required: true + type: string + functions: + description: "Comma-separated or JSON array list of benchmark function names to run" + required: true + type: string + functions_ios: + description: "Optional iOS-specific benchmark function list" + required: false + type: string + default: "" + functions_android: + description: "Optional Android-specific benchmark function list" + required: false + type: string + default: "" + iterations: + description: "Number of benchmark iterations" + required: false + type: string + default: "2" + warmup: + description: "Number of warmup iterations" + required: false + type: string + default: "1" + platform: + description: "Target platform: android, ios, or both" + required: false + type: string + default: "both" + device_profile: + description: "Device profile to run (smoke, triad, or worst)" + required: false + type: string + default: "triad" + device_profile_ios: + description: "Optional iOS-specific device profile; defaults to device_profile" + required: false + type: string + default: "" + device_profile_android: + description: "Optional Android-specific device profile; defaults to device_profile" + required: false + type: string + default: "" + rust_targets_ios: + description: "Comma-separated iOS Rust targets" + required: false + type: string + default: "aarch64-apple-ios,aarch64-apple-ios-sim,x86_64-apple-ios" + rust_targets_android: + description: "Comma-separated Android Rust targets" + required: false + type: string + default: "aarch64-linux-android" + build_release: + description: "Build in release mode" + required: false + type: boolean + default: true + mobench_version: + description: "Mobench version to install" + required: false + type: string + default: "0.1.41" + mobench_ref: + description: "Optional Git ref for mobile-bench-rs to override the released mobench install" + required: false + type: string + default: "8c3f002ae515afaa7440107f4e671f7067d276e3" + pr_number: + description: "PR number for reporting" + required: false + type: string + report_repository: + description: "owner/repo to receive the sticky benchmark comment; defaults to the workflow repository" + required: false + type: string + default: "" + requested_by: + description: "Who triggered the run" + required: false + type: string + head_sha: + description: "Exact commit SHA to checkout in the caller repo" + required: false + type: string + secrets: + BROWSERSTACK_USERNAME: + required: false + BROWSERSTACK_ACCESS_KEY: + required: false + +permissions: + actions: read + contents: write + pull-requests: write + issues: write + +env: + CARGO_TERM_COLOR: always + RUST_TOOLCHAIN: nightly-2026-03-04 + +jobs: + ios: + name: iOS BrowserStack benchmark + if: inputs.platform == 'ios' || inputs.platform == 'both' + runs-on: macos-15 + environment: Browserstack + concurrency: + group: mobench-browserstack-device-cloud + cancel-in-progress: false + env: + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + IOS_DEPLOYMENT_TARGET: "10.0" + IPHONEOS_DEPLOYMENT_TARGET: "10.0" + CFLAGS_aarch64_apple_ios: "-miphoneos-version-min=10.0" + CFLAGS_aarch64_apple_ios_sim: "-mios-simulator-version-min=10.0" + CFLAGS_x86_64_apple_ios: "-mios-simulator-version-min=10.0" + CARGO_TARGET_AARCH64_APPLE_IOS_RUSTFLAGS: "-C link-arg=-miphoneos-version-min=10.0" + CARGO_TARGET_AARCH64_APPLE_IOS_SIM_RUSTFLAGS: "-C link-arg=-mios-simulator-version-min=10.0" + CARGO_TARGET_X86_64_APPLE_IOS_RUSTFLAGS: "-C link-arg=-mios-simulator-version-min=10.0" + MOBENCH_ALLOW_UNSUPPORTED_IOS_DEPLOYMENT_TARGET: "1" + steps: + - name: Checkout caller repo + uses: actions/checkout@v4 + with: + path: caller + ref: ${{ inputs.head_sha || github.sha }} + + - name: Resolve iOS device profile + shell: bash + env: + DEVICE_PROFILE: ${{ inputs.device_profile_ios != '' && inputs.device_profile_ios || inputs.device_profile }} + ITERATIONS: ${{ inputs.iterations }} + run: | + set -euo pipefail + case "${DEVICE_PROFILE}" in + smoke) + device_specs="iPhone SE 2022-15" + fallback_device_specs="iPhone SE 2022-15" + fetch_timeout_secs="7200" + ;; + worst) + device_specs="iPhone SE 2022-15" + fallback_device_specs="iPhone SE 2022-15" + fetch_timeout_secs="7200" + ;; + triad) + device_specs="iPhone SE 2022-15,iPhone 14-16,iPhone 16 Pro Max-18" + fallback_device_specs="iPhone SE 2022-15,iPhone 14-16,iPhone 16 Pro Max-18" + fetch_timeout_secs="7200" + ;; + *) + echo "::error::Unsupported device_profile '${DEVICE_PROFILE}'. Supported values: smoke, triad, worst." + exit 1 + ;; + esac + if [[ "${ITERATIONS}" =~ ^[0-9]+$ ]] && [ "${ITERATIONS}" -ge 100 ]; then + fetch_timeout_secs="21600" + fi + + { + echo "MOBENCH_DEVICE_PROFILE=${DEVICE_PROFILE}" + echo "IOS_DEVICE_SPECS=${device_specs}" + echo "IOS_FALLBACK_DEVICE_SPECS=${fallback_device_specs}" + echo "MOBENCH_FETCH_TIMEOUT_SECS=${fetch_timeout_secs}" + } >> "$GITHUB_ENV" + + echo "Resolved iOS device profile '${DEVICE_PROFILE}' to ${device_specs}" + echo "Resolved iOS fallback devices to ${fallback_device_specs}" + echo "Resolved iOS fetch timeout to ${fetch_timeout_secs}s" + + - name: Setup Rust + shell: bash + env: + RUST_TARGETS: ${{ inputs.rust_targets_ios }} + run: | + set -euo pipefail + rustup toolchain install "${RUST_TOOLCHAIN}" --profile minimal + rustup default "${RUST_TOOLCHAIN}" + + IFS=',' read -r -a rust_targets <<<"${RUST_TARGETS}" + for target in "${rust_targets[@]}"; do + target="$(echo "$target" | xargs)" + if [[ -n "$target" ]]; then + rustup target add "$target" --toolchain "${RUST_TOOLCHAIN}" + fi + done + + rustc -Vv + cargo -V + + - name: Install mobench + shell: bash + env: + MOBENCH_VERSION: ${{ inputs.mobench_version }} + MOBENCH_REF: ${{ inputs.mobench_ref }} + run: | + set -euo pipefail + if [[ -n "${MOBENCH_REF}" ]]; then + echo "Installing mobench from ${MOBENCH_REF}" + git clone https://github.com/worldcoin/mobile-bench-rs mobench-src + git -C mobench-src fetch --depth 1 origin "${MOBENCH_REF}" + git -C mobench-src checkout FETCH_HEAD + cargo install --path mobench-src/crates/mobench --locked --force + else + echo "Installing mobench ${MOBENCH_VERSION} from crates.io" + cargo install mobench --version "${MOBENCH_VERSION}" --locked --force + fi + cargo-mobench --version + + - name: Setup Noir + uses: noir-lang/noirup@v0.1.2 + with: + toolchain: v1.0.0-beta.11 + + - name: Verify Noir + shell: bash + run: | + set -euo pipefail + export PATH="${HOME}/.nargo/bin:${PATH}" + echo "${HOME}/.nargo/bin" >> "$GITHUB_PATH" + + actions_root="$(dirname "$(dirname "$GITHUB_WORKSPACE")")/_actions" + noirup_bin="$(find "${actions_root}/noir-lang/noirup" -type f -name noirup 2>/dev/null | sort | tail -1 || true)" + + for attempt in 1 2 3; do + if command -v nargo >/dev/null 2>&1; then + nargo --version + exit 0 + fi + + if [[ -z "${noirup_bin}" ]]; then + echo "::error::noirup action binary was not found under ${actions_root}" + exit 1 + fi + + echo "::warning::nargo was not installed by noirup; retrying Noir install (${attempt}/3)" + rm -f "${HOME}/.nargo/bin/nargo" "${HOME}/.nargo/bin/bb" || true + "${noirup_bin}" --version v1.0.0-beta.11 || true + sleep $((attempt * 5)) + done + + echo "::error::nargo is still unavailable after retrying noirup" + ls -la "${HOME}/.nargo/bin" || true + exit 1 + + - name: Install iOS tooling + run: brew install xcodegen swiftformat + + - name: Generate mobile benchmark Noir artifacts + working-directory: caller + run: bench-mobile/scripts/generate-fixtures.sh + + - name: Build iOS artifacts + working-directory: caller + shell: bash + env: + RELEASE_FLAG: ${{ inputs.build_release && '--release' || '' }} + CRATE_PATH: ${{ inputs.crate_path }} + PROVEKIT_REQUIRE_MOBILE_BENCH_ARTIFACTS: "1" + run: | + set -euo pipefail + echo "Building iOS artifacts for profile ${MOBENCH_DEVICE_PROFILE}" + cargo-mobench build \ + --target ios \ + $RELEASE_FLAG \ + --crate-path "$CRATE_PATH" \ + --ios-deployment-target "${IOS_DEPLOYMENT_TARGET}" \ + --ios-runner uikit-legacy + cargo-mobench package-ipa --method adhoc --crate-path "$CRATE_PATH" + cargo-mobench package-xcuitest --crate-path "$CRATE_PATH" + test -f target/mobench/ios/BenchRunner.ipa + test -f target/mobench/ios/BenchRunnerUITests.zip + + - name: Run iOS benchmarks + id: run_ios_benchmarks + timeout-minutes: 720 + working-directory: caller + shell: bash + env: + FUNCTIONS: ${{ inputs.functions_ios != '' && inputs.functions_ios || inputs.functions }} + ITERATIONS: ${{ inputs.iterations }} + WARMUP: ${{ inputs.warmup }} + RELEASE_FLAG: ${{ inputs.build_release && '--release' || '' }} + CRATE_PATH: ${{ inputs.crate_path }} + PROVEKIT_REQUIRE_MOBILE_BENCH_ARTIFACTS: "1" + run: | + set -euo pipefail + benchmark_functions=() + if [[ "${FUNCTIONS}" == \[* ]]; then + while IFS= read -r function_name; do + if [[ -n "${function_name}" ]]; then + benchmark_functions+=("${function_name}") + fi + done < <(jq -r '.[]' <<<"${FUNCTIONS}") + else + IFS=',' read -r -a raw_functions <<<"${FUNCTIONS}" + for function_name in "${raw_functions[@]}"; do + function_name="$(echo "$function_name" | xargs)" + if [[ -n "${function_name}" ]]; then + benchmark_functions+=("${function_name}") + fi + done + fi + + if [ "${#benchmark_functions[@]}" -eq 0 ]; then + echo "::error::No iOS benchmark functions resolved from '${FUNCTIONS}'" + exit 1 + fi + + echo "Running iOS benchmarks with profile ${MOBENCH_DEVICE_PROFILE}" + echo "iOS devices: ${IOS_DEVICE_SPECS}" + echo "iOS fallback devices: ${IOS_FALLBACK_DEVICE_SPECS}" + echo "iOS fetch timeout: ${MOBENCH_FETCH_TIMEOUT_SECS}s" + max_attempts=2 + if [[ "${ITERATIONS}" =~ ^[0-9]+$ ]] && [ "${ITERATIONS}" -ge 100 ]; then + max_attempts=1 + fi + retry_sleep_secs=120 + log_dir="target/mobench/retry-logs/ios" + mkdir -p "$log_dir" + rm -rf target/mobench/ci/ios target/browserstack/ios + + is_transient_fetch_failure() { + local attempt_log="$1" + local json_path + + if grep -Eiq 'BrowserStack API .*status 5[0-9]{2}|This website is under heavy load|fetch did not recover any benchmark payloads|Timeout waiting for build .* to complete|operation timed out|Request timeout' "$attempt_log"; then + return 0 + fi + + while IFS= read -r -d '' json_path; do + if jq -e ' + if (has("status") and ((.status | ascii_downcase) == "running")) then + true + elif (.testcases?.status?.running // 0) > 0 then + true + else + false + end + ' "$json_path" >/dev/null 2>&1; then + return 0 + fi + done < <(find target/browserstack/ios -type f \( -name build.json -o -name session.json \) -print0 2>/dev/null) + + return 1 + } + + is_ios_device_schedule_failure() { + local attempt_log="$1" + grep -Eq 'os version lower than the minimum required os version|BROWSERSTACK_NO_DEVICE_FOUND_WITH_REQUESTED_CRITERIA|Device not found' "$attempt_log" + } + + make_device_args() { + local specs="$1" + device_args=() + + IFS=',' read -r -a device_specs <<<"${specs}" + for device in "${device_specs[@]}"; do + device="$(echo "$device" | xargs)" + if [[ -n "$device" ]]; then + device_args+=(--devices "$device") + fi + done + } + + effective_device_specs="${IOS_DEVICE_SPECS}" + for benchmark_function in "${benchmark_functions[@]}"; do + function_iterations="${ITERATIONS}" + function_warmup="${WARMUP}" + + function_slug="$(tr -c '[:alnum:]' '_' <<<"${benchmark_function}" | sed 's/_*$//')" + attempt=1 + while true; do + attempt_log="${log_dir}/${function_slug}-attempt-${attempt}.log" + fetch_output_dir="target/browserstack/ios/${function_slug}" + result_output_dir="target/mobench/ci/ios/${function_slug}" + rm -rf "${fetch_output_dir}" + mkdir -p "${fetch_output_dir}" "${result_output_dir}" + make_device_args "${effective_device_specs}" + + echo "mobench ios ${benchmark_function} attempt ${attempt}/${max_attempts} on ${effective_device_specs}" + set +e + cargo-mobench ci run \ + --target ios \ + --function "${benchmark_function}" \ + --iterations "${function_iterations}" \ + --warmup "${function_warmup}" \ + "${device_args[@]}" \ + --crate-path "$CRATE_PATH" \ + $RELEASE_FLAG \ + --ios-deployment-target "${IOS_DEPLOYMENT_TARGET}" \ + --ios-runner uikit-legacy \ + --fetch \ + --fetch-timeout-secs "${MOBENCH_FETCH_TIMEOUT_SECS}" \ + --fetch-output-dir "${fetch_output_dir}" \ + --output-dir "${result_output_dir}" \ + 2>&1 | tee "$attempt_log" + status=${PIPESTATUS[0]} + set -e + cp "$attempt_log" "${result_output_dir}/attempt-${attempt}.log" + + if [ "$status" -eq 0 ]; then + break + fi + + if is_ios_device_schedule_failure "$attempt_log" && [[ -n "${IOS_FALLBACK_DEVICE_SPECS}" ]] && [[ "${effective_device_specs}" != "${IOS_FALLBACK_DEVICE_SPECS}" ]]; then + echo "::warning::iOS devices ${effective_device_specs} were rejected by BrowserStack; retrying ${benchmark_function} on ${IOS_FALLBACK_DEVICE_SPECS}" + effective_device_specs="${IOS_FALLBACK_DEVICE_SPECS}" + attempt=1 + continue + fi + + if [ "$attempt" -ge "$max_attempts" ] || ! is_transient_fetch_failure "$attempt_log"; then + exit "$status" + fi + + build_id="$(grep -Eo 'Build ID: [a-f0-9]+' "$attempt_log" | awk '{print $3}' | tail -1 || true)" + if [[ -n "$build_id" ]]; then + echo "::warning::Transient BrowserStack fetch failure for iOS build ${build_id}; retrying ${benchmark_function}" + else + echo "::warning::Transient BrowserStack fetch failure for iOS; retrying ${benchmark_function}" + fi + + attempt=$((attempt + 1)) + sleep "$retry_sleep_secs" + done + done + + - name: Upload iOS results + if: always() + uses: actions/upload-artifact@v4 + with: + name: mobench-results-ios + path: | + caller/target/mobench/ci/ios/** + caller/target/browserstack/ios/** + caller/target/mobench/retry-logs/ios/** + if-no-files-found: warn + + android: + name: Android BrowserStack benchmark (${{ matrix.android_shard.function_slug }} / ${{ matrix.android_shard.device_slug }}) + needs: ios + if: always() && (inputs.platform == 'android' || inputs.platform == 'both') + runs-on: macos-14 + timeout-minutes: 100 + environment: Browserstack + strategy: + fail-fast: false + max-parallel: 1 + matrix: + android_shard: + - function: bench_mobile::bench_oprf_prove + function_slug: oprf + device: Samsung Galaxy S24-14.0 + device_slug: s24 + - function: bench_mobile::bench_oprf_prove + function_slug: oprf + device: Google Pixel 7-13.0 + device_slug: pixel7 + - function: bench_mobile::bench_oprf_prove + function_slug: oprf + device: Samsung Galaxy M32-11.0 + device_slug: galaxy-m32 + - function: bench_mobile::bench_passport_fragmented_age_check_prove + function_slug: fragmented + device: Samsung Galaxy S24-14.0 + device_slug: s24 + - function: bench_mobile::bench_passport_fragmented_age_check_prove + function_slug: fragmented + device: Google Pixel 7-13.0 + device_slug: pixel7 + - function: bench_mobile::bench_passport_fragmented_age_check_prove + function_slug: fragmented + device: Samsung Galaxy M32-11.0 + device_slug: galaxy-m32 + - function: bench_mobile::bench_passport_complete_age_check_prove + function_slug: monolithic + device: Samsung Galaxy S24-14.0 + device_slug: s24 + - function: bench_mobile::bench_passport_complete_age_check_prove + function_slug: monolithic + device: Google Pixel 7-13.0 + device_slug: pixel7 + - function: bench_mobile::bench_passport_complete_age_check_prove + function_slug: monolithic + device: Samsung Galaxy M32-11.0 + device_slug: galaxy-m32 + env: + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + steps: + - name: Checkout caller repo + uses: actions/checkout@v4 + with: + path: caller + ref: ${{ inputs.head_sha || github.sha }} + + - name: Resolve Android device profile + shell: bash + env: + DEVICE_PROFILE: ${{ inputs.device_profile_android != '' && inputs.device_profile_android || inputs.device_profile }} + run: | + set -euo pipefail + case "${DEVICE_PROFILE}" in + smoke) + device_specs="Samsung Galaxy M32-11.0" + fetch_timeout_secs="900" + ;; + worst) + device_specs="Samsung Galaxy M32-11.0" + fetch_timeout_secs="900" + ;; + triad) + device_specs="Samsung Galaxy S24-14.0,Google Pixel 7-13.0,Samsung Galaxy M32-11.0" + fetch_timeout_secs="900" + ;; + *) + echo "::error::Unsupported device_profile '${DEVICE_PROFILE}'. Supported values: smoke, triad, worst." + exit 1 + ;; + esac + + { + echo "MOBENCH_DEVICE_PROFILE=${DEVICE_PROFILE}" + echo "ANDROID_DEVICE_SPECS=${device_specs}" + echo "MOBENCH_FETCH_TIMEOUT_SECS=${fetch_timeout_secs}" + } >> "$GITHUB_ENV" + + echo "Resolved Android device profile '${DEVICE_PROFILE}' to ${device_specs}" + echo "Resolved Android fetch timeout to ${fetch_timeout_secs}s" + + - name: Setup Rust + shell: bash + env: + RUST_TARGETS: ${{ inputs.rust_targets_android }} + run: | + set -euo pipefail + rustup toolchain install "${RUST_TOOLCHAIN}" --profile minimal + rustup default "${RUST_TOOLCHAIN}" + + IFS=',' read -r -a rust_targets <<<"${RUST_TARGETS}" + for target in "${rust_targets[@]}"; do + target="$(echo "$target" | xargs)" + if [[ -n "$target" ]]; then + rustup target add "$target" --toolchain "${RUST_TOOLCHAIN}" + fi + done + + rustc -Vv + cargo -V + + - name: Setup Android SDK/NDK + uses: android-actions/setup-android@v3 + + - name: Install SDK packages and resolve NDK + shell: bash + run: | + SDKMGR="${ANDROID_HOME}/cmdline-tools/latest/bin/sdkmanager" + if [ ! -x "$SDKMGR" ]; then + SDKMGR=$(command -v sdkmanager 2>/dev/null || echo "sdkmanager") + fi + + $SDKMGR --install "platform-tools" "platforms;android-34" "build-tools;34.0.0" "ndk;26.1.10909125" 2>&1 || true + + if [ -d "${ANDROID_HOME}/ndk/26.1.10909125" ]; then + NDK_DIR="${ANDROID_HOME}/ndk/26.1.10909125" + else + NDK_VER=$(ls "${ANDROID_HOME}/ndk/" 2>/dev/null | sort -V | tail -1) + if [ -z "$NDK_VER" ]; then + echo "::error::No Android NDK found" + exit 1 + fi + NDK_DIR="${ANDROID_HOME}/ndk/${NDK_VER}" + fi + + echo "ANDROID_NDK_HOME=${NDK_DIR}" >> "$GITHUB_ENV" + echo "ANDROID_NDK_ROOT=${NDK_DIR}" >> "$GITHUB_ENV" + + - name: Install cargo-ndk + run: cargo install cargo-ndk --locked + + - name: Install mobench + shell: bash + env: + MOBENCH_VERSION: ${{ inputs.mobench_version }} + MOBENCH_REF: ${{ inputs.mobench_ref }} + run: | + set -euo pipefail + if [[ -n "${MOBENCH_REF}" ]]; then + echo "Installing mobench from ${MOBENCH_REF}" + git clone https://github.com/worldcoin/mobile-bench-rs mobench-src + git -C mobench-src fetch --depth 1 origin "${MOBENCH_REF}" + git -C mobench-src checkout FETCH_HEAD + cargo install --path mobench-src/crates/mobench --locked --force + else + echo "Installing mobench ${MOBENCH_VERSION} from crates.io" + cargo install mobench --version "${MOBENCH_VERSION}" --locked --force + fi + cargo-mobench --version + + - name: Setup Noir + uses: noir-lang/noirup@v0.1.2 + with: + toolchain: v1.0.0-beta.11 + + - name: Verify Noir + shell: bash + run: | + set -euo pipefail + export PATH="${HOME}/.nargo/bin:${PATH}" + echo "${HOME}/.nargo/bin" >> "$GITHUB_PATH" + + actions_root="$(dirname "$(dirname "$GITHUB_WORKSPACE")")/_actions" + noirup_bin="$(find "${actions_root}/noir-lang/noirup" -type f -name noirup 2>/dev/null | sort | tail -1 || true)" + + for attempt in 1 2 3; do + if command -v nargo >/dev/null 2>&1; then + nargo --version + exit 0 + fi + + if [[ -z "${noirup_bin}" ]]; then + echo "::error::noirup action binary was not found under ${actions_root}" + exit 1 + fi + + echo "::warning::nargo was not installed by noirup; retrying Noir install (${attempt}/3)" + rm -f "${HOME}/.nargo/bin/nargo" "${HOME}/.nargo/bin/bb" || true + "${noirup_bin}" --version v1.0.0-beta.11 || true + sleep $((attempt * 5)) + done + + echo "::error::nargo is still unavailable after retrying noirup" + ls -la "${HOME}/.nargo/bin" || true + exit 1 + + - name: Generate mobile benchmark Noir artifacts + working-directory: caller + run: bench-mobile/scripts/generate-fixtures.sh + + - name: Build Android artifacts + working-directory: caller + shell: bash + env: + RELEASE_FLAG: ${{ inputs.build_release && '--release' || '' }} + CRATE_PATH: ${{ inputs.crate_path }} + MOBENCH_ANDROID_BENCHMARK_TIMEOUT_SECS: "600" + MOBENCH_ANDROID_HEARTBEAT_INTERVAL_SECS: "10" + PROVEKIT_REQUIRE_MOBILE_BENCH_ARTIFACTS: "1" + run: | + set -euo pipefail + + is_transient_android_build_failure() { + local log_path="$1" + grep -Eiq 'dl\.google\.com|services\.gradle\.org|Could not GET|nodename nor servname provided|Temporary failure in name resolution|Read timed out|Connection reset|Could not resolve com\.android\.tools\.build:aapt2' "$log_path" + } + + for attempt in 1 2 3; do + log_path="target/mobench/android-build-attempt-${attempt}.log" + mkdir -p "$(dirname "$log_path")" + echo "Android build attempt ${attempt}/3" + + set +e + cargo-mobench build --target android $RELEASE_FLAG --crate-path "$CRATE_PATH" 2>&1 | tee "$log_path" + status="${PIPESTATUS[0]}" + set -e + + if [ "$status" -eq 0 ]; then + exit 0 + fi + + if [ "$attempt" -ge 3 ] || ! is_transient_android_build_failure "$log_path"; then + exit "$status" + fi + + echo "::warning::Transient Android/Gradle build dependency fetch failure; retrying after backoff" + sleep $((attempt * 15)) + done + + - name: Run Android benchmarks + id: run_android_benchmarks + timeout-minutes: 100 + working-directory: caller + shell: bash + env: + FUNCTIONS: ${{ matrix.android_shard.function }} + ANDROID_DEVICE_SPEC: ${{ matrix.android_shard.device }} + ITERATIONS: ${{ inputs.iterations }} + WARMUP: ${{ inputs.warmup }} + RELEASE_FLAG: ${{ inputs.build_release && '--release' || '' }} + CRATE_PATH: ${{ inputs.crate_path }} + PROVEKIT_REQUIRE_MOBILE_BENCH_ARTIFACTS: "1" + run: | + set -euo pipefail + + benchmark_functions=() + if [[ "${FUNCTIONS}" == \[* ]]; then + while IFS= read -r function_name; do + if [[ -n "${function_name}" ]]; then + benchmark_functions+=("${function_name}") + fi + done < <(jq -r '.[]' <<<"${FUNCTIONS}") + else + IFS=',' read -r -a raw_functions <<<"${FUNCTIONS}" + for function_name in "${raw_functions[@]}"; do + function_name="$(echo "$function_name" | xargs)" + if [[ -n "${function_name}" ]]; then + benchmark_functions+=("${function_name}") + fi + done + fi + + if [ "${#benchmark_functions[@]}" -eq 0 ]; then + echo "::error::No Android benchmark functions resolved from '${FUNCTIONS}'" + exit 1 + fi + + echo "Running Android benchmarks with profile ${MOBENCH_DEVICE_PROFILE}" + if [[ -n "${ANDROID_DEVICE_SPEC:-}" ]]; then + ANDROID_DEVICE_SPECS="${ANDROID_DEVICE_SPEC}" + fi + echo "Android devices: ${ANDROID_DEVICE_SPECS}" + echo "Android fetch timeout: ${MOBENCH_FETCH_TIMEOUT_SECS}s" + max_attempts=1 + retry_sleep_secs=120 + log_dir="target/mobench/retry-logs/android" + mkdir -p "$log_dir" + rm -rf target/mobench/ci/android target/browserstack/android + failure_count=0 + + is_transient_fetch_failure() { + local attempt_log="$1" + local json_path + + if grep -Eiq 'BrowserStack API .*status 5[0-9]{2}|requesting BrowserStack API|This website is under heavy load|fetch did not recover any benchmark payloads|No benchmark results found|Timeout waiting for build .* to complete|operation timed out|Request timeout' "$attempt_log"; then + return 0 + fi + + while IFS= read -r -d '' json_path; do + if jq -e ' + if (has("status") and ((.status | ascii_downcase) == "running")) then + true + elif (.testcases?.status?.running // 0) > 0 then + true + else + false + end + ' "$json_path" >/dev/null 2>&1; then + return 0 + fi + done < <(find target/browserstack/android -type f \( -name build.json -o -name session.json \) -print0 2>/dev/null) + + return 1 + } + + android_devices=() + IFS=',' read -r -a device_specs <<<"${ANDROID_DEVICE_SPECS}" + for device in "${device_specs[@]}"; do + device="$(echo "$device" | xargs)" + if [[ -n "$device" ]]; then + android_devices+=("$device") + fi + done + + if [ "${#android_devices[@]}" -eq 0 ]; then + echo "::error::No Android devices resolved from '${ANDROID_DEVICE_SPECS}'" + exit 1 + fi + + write_android_failure() { + local result_output_dir="$1" + local attempt_log="$2" + local fetch_output_dir="$3" + local benchmark_function="$4" + local android_device="$5" + local function_iterations="$6" + local function_warmup="$7" + local function_max_attempts="$8" + local function_fetch_timeout_secs="$9" + + mkdir -p "${result_output_dir}" + local build_id + local waited_secs + local kill_evidence + local failure_reason + + build_id="$(grep -Eo 'Build ID: [a-f0-9]+' "$attempt_log" | awk '{print $3}' | tail -1 || true)" + waited_secs="$(grep -Eo 'waited [0-9]+ seconds' "$attempt_log" | awk '{print $2}' | tail -1 || true)" + if [[ -z "${waited_secs}" ]]; then + waited_secs="${function_fetch_timeout_secs}" + fi + kill_evidence="$( + { + grep -Eih 'lowmemorykiller|low memory|Process .* was killed|Process .* killed|oom_reaper|SIGKILL|signal [0-9]+|out of memory|lmk' "$attempt_log" 2>/dev/null || true + find "${fetch_output_dir}" -type f -name '*.log' -exec grep -Eih 'lowmemorykiller|low memory|Process .* was killed|Process .* killed|oom_reaper|SIGKILL|signal [0-9]+|out of memory|lmk' {} + 2>/dev/null || true + } | tail -5 | tr '\n' ' ' + )" + if [[ -n "${kill_evidence}" ]]; then + failure_reason="Android benchmark process reported an abnormal kill before results were fetched after ${waited_secs}s: ${kill_evidence}" + else + failure_reason="Android benchmark process produced no summary.json before the ${waited_secs}s BrowserStack fetch timeout; likely app/process death or watchdog timeout. Check BrowserStack build logs for LMK/OOM/SIGKILL evidence." + fi + { + echo "### Android fixture incomplete" + echo "" + echo "- Function: \`${benchmark_function}\`" + echo "- Device: \`${android_device}\`" + echo "- Iterations/Warmup: \`${function_iterations} / ${function_warmup}\`" + echo "- Fetch timeout: \`${waited_secs}s\`" + if [[ -n "${build_id}" ]]; then + echo "- BrowserStack build: \`${build_id}\`" + fi + echo "- Reason: ${failure_reason}" + } > "${result_output_dir}/failure.md" + jq -n \ + --arg platform "android" \ + --arg function "${benchmark_function}" \ + --arg devices "${android_device}" \ + --arg attempts "${function_max_attempts}" \ + --arg fetch_timeout_secs "${waited_secs}" \ + --arg build_id "${build_id}" \ + --arg reason "${failure_reason}" \ + --arg kill_evidence "${kill_evidence}" \ + '{ + platform: $platform, + function: $function, + devices: $devices, + attempts: ($attempts | tonumber), + fetch_timeout_secs: ($fetch_timeout_secs | tonumber), + build_id: (if $build_id == "" then null else $build_id end), + reason: $reason, + kill_evidence: (if $kill_evidence == "" then null else $kill_evidence end) + }' \ + > "${result_output_dir}/failure.json" + } + + for benchmark_function in "${benchmark_functions[@]}"; do + ordered_android_devices=("${android_devices[@]}") + if [[ "${benchmark_function}" == "bench_mobile::bench_passport_complete_age_check_prove" ]]; then + ordered_android_devices=() + declare -a deferred_android_devices=() + for android_device in "${android_devices[@]}"; do + if [[ "${android_device}" == "Samsung Galaxy M32-11.0" ]]; then + deferred_android_devices+=("${android_device}") + else + ordered_android_devices+=("${android_device}") + fi + done + if [ "${#deferred_android_devices[@]}" -gt 0 ]; then + ordered_android_devices+=("${deferred_android_devices[@]}") + fi + fi + + for android_device in "${ordered_android_devices[@]}"; do + function_iterations="${ITERATIONS}" + function_warmup="${WARMUP}" + function_fetch_timeout_secs="${MOBENCH_FETCH_TIMEOUT_SECS}" + function_max_attempts="${max_attempts}" + function_android_timeout_secs="900" + if [[ "${benchmark_function}" == "bench_mobile::bench_passport_complete_age_check_prove" ]]; then + function_fetch_timeout_secs="1800" + function_android_timeout_secs="1800" + function_max_attempts="1" + fi + if [[ "${android_device}" == "Samsung Galaxy M32-11.0" && ( "${benchmark_function}" == "bench_mobile::bench_passport_complete_age_check_prove" || "${benchmark_function}" == "bench_mobile::bench_passport_fragmented_age_check_prove" ) ]]; then + function_fetch_timeout_secs="1800" + function_android_timeout_secs="1800" + function_max_attempts="1" + fi + if [[ "${benchmark_function}" == "bench_mobile::bench_oprf_prove" ]]; then + function_max_attempts="2" + fi + if [[ "${function_iterations}" =~ ^[0-9]+$ ]] && [ "${function_iterations}" -ge 100 ]; then + function_fetch_timeout_secs="6000" + function_android_timeout_secs="6000" + function_max_attempts="1" + fi + + function_slug="$(tr -c '[:alnum:]' '_' <<<"${benchmark_function}" | sed 's/_*$//')" + device_slug="$(tr -c '[:alnum:]' '_' <<<"${android_device}" | sed 's/_*$//')" + split_low_tier_age_samples="false" + if [[ "${android_device}" == "Samsung Galaxy M32-11.0" && ( "${benchmark_function}" == "bench_mobile::bench_passport_complete_age_check_prove" || "${benchmark_function}" == "bench_mobile::bench_passport_fragmented_age_check_prove" ) ]]; then + split_low_tier_age_samples="true" + fi + if [[ "${android_device}" == "Google Pixel 7-13.0" && "${benchmark_function}" == "bench_mobile::bench_passport_complete_age_check_prove" ]]; then + split_low_tier_age_samples="true" + fi + + if [[ "${split_low_tier_age_samples}" == "true" ]]; then + result_output_dir="target/mobench/ci/android/${function_slug}/${device_slug}" + split_output_dir="${result_output_dir}/split" + warmup_output_base="target/mobench/split-warmup/android/${function_slug}/${device_slug}" + rm -rf "${result_output_dir}" "target/browserstack/android/${function_slug}/${device_slug}" "${warmup_output_base}" "target/browserstack/android-warmup/${function_slug}/${device_slug}" + mkdir -p "${split_output_dir}" "${warmup_output_base}" + split_failed="false" + + run_split_invocation() { + local kind="$1" + local index="$2" + local output_dir="$3" + local fetch_output_dir="$4" + local split_max_attempts="2" + local attempt status log_path build_id + + for ((attempt = 1; attempt <= split_max_attempts; attempt++)); do + log_path="${log_dir}/${function_slug}-${device_slug}-${kind}-${index}-attempt-${attempt}.log" + rm -rf "${output_dir}" "${fetch_output_dir}" + mkdir -p "${output_dir}" "${fetch_output_dir}" + awk -v timeout="${function_android_timeout_secs}" -v heartbeat="10" ' + /^android_benchmark_timeout_secs[[:space:]]*=/ { print "android_benchmark_timeout_secs = " timeout; next } + /^android_heartbeat_interval_secs[[:space:]]*=/ { print "android_heartbeat_interval_secs = " heartbeat; next } + { print } + ' mobench.toml > mobench.ci.toml + mv mobench.ci.toml mobench.toml + + echo "mobench android split ${kind} ${index} attempt ${attempt}/${split_max_attempts}: ${benchmark_function} on ${android_device}" + set +e + MOBENCH_ANDROID_BENCHMARK_TIMEOUT_SECS="${function_android_timeout_secs}" \ + MOBENCH_ANDROID_HEARTBEAT_INTERVAL_SECS="10" \ + cargo-mobench ci run \ + --target android \ + --function "${benchmark_function}" \ + --iterations 1 \ + --warmup 0 \ + --devices "${android_device}" \ + --crate-path "$CRATE_PATH" \ + $RELEASE_FLAG \ + --android-benchmark-timeout-secs "${function_android_timeout_secs}" \ + --android-heartbeat-interval-secs 10 \ + --fetch \ + --fetch-timeout-secs "${function_fetch_timeout_secs}" \ + --fetch-output-dir "${fetch_output_dir}" \ + --output-dir "${output_dir}" \ + 2>&1 | tee "$log_path" + status=${PIPESTATUS[0]} + set -e + cp "$log_path" "${output_dir}/attempt-${attempt}.log" + if [ "$status" -eq 0 ]; then + return 0 + fi + if [ "$attempt" -ge "$split_max_attempts" ] || ! is_transient_fetch_failure "$log_path"; then + write_android_failure "${result_output_dir}" "$log_path" "${fetch_output_dir}" "${benchmark_function}" "${android_device}" "${function_iterations}" "${function_warmup}" "${function_max_attempts}" "${function_fetch_timeout_secs}" + return "$status" + fi + build_id="$(grep -Eo 'Build ID: [a-f0-9]+' "$log_path" | awk '{print $3}' | tail -1 || true)" + if [[ -n "$build_id" ]]; then + echo "::warning::Transient BrowserStack fetch failure for split Android build ${build_id}; retrying ${benchmark_function} on ${android_device} (${kind} ${index}, attempt ${attempt}/${split_max_attempts})" + else + echo "::warning::Transient BrowserStack fetch failure for split Android benchmark; retrying ${benchmark_function} on ${android_device} (${kind} ${index}, attempt ${attempt}/${split_max_attempts})" + fi + sleep $((attempt * 15)) + done + } + + for ((warmup_index = 1; warmup_index <= function_warmup; warmup_index++)); do + if ! run_split_invocation "warmup" "${warmup_index}" "${warmup_output_base}/warmup-${warmup_index}" "target/browserstack/android-warmup/${function_slug}/${device_slug}/warmup-${warmup_index}"; then + split_failed="true" + break + fi + done + + if [[ "${split_failed}" != "true" ]]; then + for ((sample_index = 1; sample_index <= function_iterations; sample_index++)); do + if ! run_split_invocation "sample" "${sample_index}" "${split_output_dir}/sample-${sample_index}" "target/browserstack/android/${function_slug}/${device_slug}/sample-${sample_index}"; then + split_failed="true" + break + fi + done + fi + + if [[ "${split_failed}" == "true" ]]; then + failure_count=$((failure_count + 1)) + continue + fi + + python3 .github/scripts/merge_mobench_split_runs.py \ + --samples-dir "${split_output_dir}" \ + --output-dir "${result_output_dir}" \ + --function "${benchmark_function}" \ + --device "${android_device}" \ + --iterations "${function_iterations}" \ + --warmup "${function_warmup}" + continue + fi + + attempt=1 + while true; do + attempt_log="${log_dir}/${function_slug}-${device_slug}-attempt-${attempt}.log" + fetch_output_dir="target/browserstack/android/${function_slug}/${device_slug}" + result_output_dir="target/mobench/ci/android/${function_slug}/${device_slug}" + rm -rf "${fetch_output_dir}" + mkdir -p "${fetch_output_dir}" "${result_output_dir}" + awk -v timeout="${function_android_timeout_secs}" -v heartbeat="10" ' + /^android_benchmark_timeout_secs[[:space:]]*=/ { print "android_benchmark_timeout_secs = " timeout; next } + /^android_heartbeat_interval_secs[[:space:]]*=/ { print "android_heartbeat_interval_secs = " heartbeat; next } + { print } + ' mobench.toml > mobench.ci.toml + mv mobench.ci.toml mobench.toml + + echo "mobench android ${benchmark_function} on ${android_device} attempt ${attempt}/${function_max_attempts}" + set +e + MOBENCH_ANDROID_BENCHMARK_TIMEOUT_SECS="${function_android_timeout_secs}" \ + MOBENCH_ANDROID_HEARTBEAT_INTERVAL_SECS="10" \ + cargo-mobench ci run \ + --target android \ + --function "${benchmark_function}" \ + --iterations "${function_iterations}" \ + --warmup "${function_warmup}" \ + --devices "${android_device}" \ + --crate-path "$CRATE_PATH" \ + $RELEASE_FLAG \ + --android-benchmark-timeout-secs "${function_android_timeout_secs}" \ + --android-heartbeat-interval-secs 10 \ + --fetch \ + --fetch-timeout-secs "${function_fetch_timeout_secs}" \ + --fetch-output-dir "${fetch_output_dir}" \ + --output-dir "${result_output_dir}" \ + 2>&1 | tee "$attempt_log" + status=${PIPESTATUS[0]} + set -e + cp "$attempt_log" "${result_output_dir}/attempt-${attempt}.log" + + if [ "$status" -eq 0 ]; then + break + fi + + if [ "$attempt" -ge "$function_max_attempts" ] && grep -Eiq 'No benchmark results found|Timeout waiting for build|Build .* failed with status: failed' "$attempt_log"; then + echo "::warning::Android ${benchmark_function} did not return benchmark results on ${android_device}; preserving partial fixture results" + failure_count=$((failure_count + 1)) + build_id="$(grep -Eo 'Build ID: [a-f0-9]+' "$attempt_log" | awk '{print $3}' | tail -1 || true)" + waited_secs="$(grep -Eo 'waited [0-9]+ seconds' "$attempt_log" | awk '{print $2}' | tail -1 || true)" + if [[ -z "${waited_secs}" ]]; then + waited_secs="${function_fetch_timeout_secs}" + fi + kill_evidence="$( + { + grep -Eih 'lowmemorykiller|low memory|Process .* was killed|Process .* killed|oom_reaper|SIGKILL|signal [0-9]+|out of memory|lmk' "$attempt_log" 2>/dev/null || true + find "${fetch_output_dir}" -type f -name '*.log' -exec grep -Eih 'lowmemorykiller|low memory|Process .* was killed|Process .* killed|oom_reaper|SIGKILL|signal [0-9]+|out of memory|lmk' {} + 2>/dev/null || true + } | tail -5 | tr '\n' ' ' + )" + if [[ -n "${kill_evidence}" ]]; then + failure_reason="Android benchmark process reported an abnormal kill before results were fetched after ${waited_secs}s: ${kill_evidence}" + else + failure_reason="Android benchmark process produced no summary.json before the ${waited_secs}s BrowserStack fetch timeout; likely app/process death or watchdog timeout. Check BrowserStack build logs for LMK/OOM/SIGKILL evidence." + fi + { + echo "### Android fixture incomplete" + echo "" + echo "- Function: \`${benchmark_function}\`" + echo "- Device: \`${android_device}\`" + echo "- Iterations/Warmup: \`${function_iterations} / ${function_warmup}\`" + echo "- Fetch timeout: \`${waited_secs}s\`" + if [[ -n "${build_id}" ]]; then + echo "- BrowserStack build: \`${build_id}\`" + fi + echo "- Reason: ${failure_reason}" + } > "${result_output_dir}/failure.md" + jq -n \ + --arg platform "android" \ + --arg function "${benchmark_function}" \ + --arg devices "${android_device}" \ + --arg attempts "${function_max_attempts}" \ + --arg fetch_timeout_secs "${waited_secs}" \ + --arg build_id "${build_id}" \ + --arg reason "${failure_reason}" \ + --arg kill_evidence "${kill_evidence}" \ + '{ + platform: $platform, + function: $function, + devices: $devices, + attempts: ($attempts | tonumber), + fetch_timeout_secs: ($fetch_timeout_secs | tonumber), + build_id: (if $build_id == "" then null else $build_id end), + reason: $reason, + kill_evidence: (if $kill_evidence == "" then null else $kill_evidence end) + }' \ + > "${result_output_dir}/failure.json" + break + fi + + if [ "$attempt" -ge "$function_max_attempts" ] || ! is_transient_fetch_failure "$attempt_log"; then + exit "$status" + fi + + build_id="$(grep -Eo 'Build ID: [a-f0-9]+' "$attempt_log" | awk '{print $3}' | tail -1 || true)" + if [[ -n "$build_id" ]]; then + echo "::warning::Transient BrowserStack fetch failure for Android build ${build_id}; retrying ${benchmark_function} on ${android_device}" + else + echo "::warning::Transient BrowserStack fetch failure for Android; retrying ${benchmark_function} on ${android_device}" + fi + + attempt=$((attempt + 1)) + sleep "$retry_sleep_secs" + done + done + done + + if [ "${failure_count}" -gt 0 ]; then + echo "::error::Android benchmark completed with ${failure_count} fixture failure(s)." + exit 1 + fi + + - name: Upload Android results + if: always() + uses: actions/upload-artifact@v4 + with: + name: mobench-results-android-${{ matrix.android_shard.function_slug }}-${{ matrix.android_shard.device_slug }} + path: | + caller/target/mobench/ci/android/** + caller/target/browserstack/android/** + caller/target/mobench/retry-logs/android/** + if-no-files-found: warn + + summarize: + name: Summarize benchmark results + needs: [ios, android] + if: always() + runs-on: ubuntu-latest + steps: + - name: Checkout caller repo + uses: actions/checkout@v4 + with: + path: caller + ref: ${{ inputs.head_sha || github.sha }} + + - name: Setup Rust + shell: bash + run: | + set -euo pipefail + rustup toolchain install "${RUST_TOOLCHAIN}" --profile minimal + rustup default "${RUST_TOOLCHAIN}" + rustc -Vv + cargo -V + + - name: Install mobench + shell: bash + env: + MOBENCH_VERSION: ${{ inputs.mobench_version }} + MOBENCH_REF: ${{ inputs.mobench_ref }} + run: | + set -euo pipefail + if [[ -n "${MOBENCH_REF}" ]]; then + echo "Installing mobench from ${MOBENCH_REF}" + git clone https://github.com/worldcoin/mobile-bench-rs mobench-src + git -C mobench-src fetch --depth 1 origin "${MOBENCH_REF}" + git -C mobench-src checkout FETCH_HEAD + cargo install --path mobench-src/crates/mobench --locked --force + else + echo "Installing mobench ${MOBENCH_VERSION} from crates.io" + cargo install mobench --version "${MOBENCH_VERSION}" --locked --force + fi + cargo-mobench --version + + - name: Download iOS results + if: always() + continue-on-error: true + uses: actions/download-artifact@v4 + with: + name: mobench-results-ios + path: results/ios + + - name: Download Android results + if: always() + continue-on-error: true + uses: actions/download-artifact@v4 + with: + pattern: mobench-results-android-* + path: results/android + merge-multiple: true + + - name: Setup Python for plot rendering + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install plot rendering dependencies + shell: bash + run: | + python -m pip install --upgrade pip + python -m pip install matplotlib numpy + + - name: Render plot-capable platform summaries + id: render_summaries + shell: bash + run: | + set -euo pipefail + mkdir -p rendered + rendered_count=0 + + render_platform_summary() { + local platform="$1" + local results_dir="results/${platform}" + if [ ! -d "${results_dir}" ]; then + return 0 + fi + + local summary_json + summary_json=$(find "${results_dir}" -type f -name summary.json | head -1) + if [ -z "${summary_json}" ]; then + echo "::warning::No ${platform} summary.json found under ${results_dir}" + return 0 + fi + + local results_csv + results_csv=$(find "${results_dir}" -type f -name results.csv | head -1) + if [ -z "${results_csv}" ]; then + echo "::warning::No ${platform} results.csv found under ${results_dir}" + return 0 + fi + + if ! jq -e ' + [ + ((.device_summaries // []) | length), + ((.summary?.device_summaries // []) | length) + ] | max > 0 + ' "${summary_json}" >/dev/null; then + echo "::warning::Skipping ${platform} summary render because device_summaries is empty." + return 0 + fi + + local csv_line_count + csv_line_count=$(wc -l < "${results_csv}" | tr -d ' ') + if [ "${csv_line_count}" -le 1 ]; then + echo "::warning::Skipping ${platform} summary render because results.csv has no data rows." + return 0 + fi + + mkdir -p "rendered/${platform}" + + if cargo-mobench report summarize \ + --summary "${summary_json}" \ + --output "rendered/${platform}/summary.md" \ + --plots auto; then + return 0 + fi + + echo "::warning::Falling back to markdown-only ${platform} summary." + cargo-mobench ci summarize \ + --results-dir "${results_dir}" \ + --output-format markdown \ + --output-file "rendered/${platform}/summary.md" + } + + for platform in ios android; do + if render_platform_summary "${platform}" && [ -f "rendered/${platform}/summary.md" ]; then + rendered_count=$((rendered_count + 1)) + fi + done + + if [ "${rendered_count}" -eq 0 ]; then + echo "::warning::No benchmark summaries were rendered." + fi + + echo "rendered_count=${rendered_count}" >> "$GITHUB_OUTPUT" + + - name: Render platform Sina plots + if: steps.render_summaries.outputs.rendered_count != '0' + shell: bash + run: | + set -euo pipefail + + python <<'PY' + import json + import math + import re + from collections import defaultdict + from pathlib import Path + + import matplotlib.pyplot as plt + import numpy as np + + FUNCTION_LABELS = { + "bench_mobile::bench_oprf_prove": "OPRF", + "bench_mobile::bench_passport_fragmented_age_check_prove": "Fragmented age", + "bench_mobile::bench_passport_complete_age_check_prove": "Monolithic age", + } + FUNCTION_COLORS = { + "OPRF": "#2f80ed", + "Fragmented age": "#27ae60", + "Monolithic age": "#eb5757", + } + + def report_function(report): + name = report.get("function") or report.get("spec", {}).get("name") or "benchmark" + return FUNCTION_LABELS.get(name, name.rsplit("::", 1)[-1]) + + def android_device_from_path(report_path): + parts = report_path.parts + try: + idx = parts.index("android") + device_slug = parts[idx + 2] + except (ValueError, IndexError): + return "Android" + device_slug = re.sub(r"_\d+_\d+$", "", device_slug) + return device_slug.replace("_", " ") + + def ios_session_devices(build_json_path): + try: + build = json.loads(build_json_path.read_text()) + except (OSError, json.JSONDecodeError): + return {} + + mapping = {} + for device in build.get("devices", []): + label = device.get("device") or "iPhone" + for session in device.get("sessions", []): + session_id = session.get("id") + if session_id: + mapping[session_id] = label + return mapping + + def ios_device_from_path(report_path, cache): + session_dir = report_path.parent + session_id = session_dir.name.removeprefix("session-") + build_dir = session_dir.parent + build_json_path = build_dir / "build.json" + if build_json_path not in cache: + cache[build_json_path] = ios_session_devices(build_json_path) + return cache[build_json_path].get(session_id, "iPhone") + + def sample_points(platform): + root = Path("results") / platform / "browserstack" / platform + if not root.exists(): + return [] + + points = [] + ios_build_cache = {} + for report_path in sorted(root.rglob("bench-report.json")): + try: + report = json.loads(report_path.read_text()) + except (OSError, json.JSONDecodeError): + continue + + function = report_function(report) + if platform == "ios": + device = ios_device_from_path(report_path, ios_build_cache) + else: + device = android_device_from_path(report_path) + + samples = report.get("samples") or [] + if not samples and report.get("samples_ns"): + samples = [{"duration_ns": duration_ns} for duration_ns in report["samples_ns"]] + + for index, sample in enumerate(samples): + duration_ns = sample.get("duration_ns") + if duration_ns is None and index < len(report.get("samples_ns", [])): + duration_ns = report["samples_ns"][index] + if duration_ns is None: + continue + + memory_kb = sample.get("process_peak_memory_kb") + if memory_kb is None: + memory_kb = sample.get("peak_memory_kb") + + points.append( + { + "device": device, + "function": function, + "duration_s": duration_ns / 1_000_000_000.0, + "memory_mb": None if memory_kb is None else memory_kb / 1024.0, + } + ) + return points + + def sina_offsets(values, max_width=0.32): + if len(values) <= 1: + return np.zeros(len(values)) + + values = np.asarray(values, dtype=float) + finite = values[np.isfinite(values)] + if len(finite) <= 1 or float(np.nanmax(finite) - np.nanmin(finite)) == 0.0: + widths = np.full(len(values), max_width * 0.45) + else: + bins = min(24, max(6, int(math.sqrt(len(finite)) * 2))) + counts, edges = np.histogram(finite, bins=bins) + max_count = max(int(counts.max()), 1) + bin_index = np.clip(np.searchsorted(edges, values, side="right") - 1, 0, len(counts) - 1) + widths = np.array([(counts[i] / max_count) * max_width for i in bin_index], dtype=float) + + rng = np.random.default_rng(42) + return rng.uniform(-widths, widths) + + def device_sort_key(device): + known_order = [ + "iPhone 7", + "iPhone SE", + "iPhone 11", + "iPhone 13", + "iPhone 14", + "iPhone 15", + "iPhone 16", + "iPhone 17", + "Samsung Galaxy M32", + "Google Pixel 7", + "Samsung Galaxy S24", + ] + for index, prefix in enumerate(known_order): + if device.startswith(prefix): + return (index, device) + return (len(known_order), device) + + def draw_axis(ax, grouped, categories, metric, ylabel): + for xpos, category in enumerate(categories): + records = grouped[category] + values = [record[metric] for record in records if record[metric] is not None] + if not values: + continue + + offsets = sina_offsets(values) + colors = [FUNCTION_COLORS.get(record["function"], "#555555") for record in records if record[metric] is not None] + ax.scatter( + xpos + offsets, + values, + s=24, + c=colors, + alpha=0.62, + edgecolors="white", + linewidths=0.35, + zorder=3, + ) + median = float(np.median(values)) + ax.plot([xpos - 0.22, xpos + 0.22], [median, median], color="#111111", linewidth=1.6, zorder=4) + + ax.set_ylabel(ylabel) + ax.grid(axis="y", color="#d9dee7", linewidth=0.8) + ax.set_axisbelow(True) + + def render_platform(platform, title): + points = sample_points(platform) + if not points: + return + + grouped = defaultdict(list) + for point in points: + grouped[(point["device"], point["function"])].append(point) + + devices = sorted({point["device"] for point in points}, key=device_sort_key) + functions = [label for label in FUNCTION_LABELS.values() if label in {point["function"] for point in points}] + functions += sorted({point["function"] for point in points} - set(functions)) + categories = [(device, function) for device in devices for function in functions if (device, function) in grouped] + + output_dir = Path("rendered") / platform / "plots" + output_dir.mkdir(parents=True, exist_ok=True) + + width = max(10.0, len(categories) * 0.95) + fig, axes = plt.subplots(2, 1, figsize=(width, 9.5), sharex=True, constrained_layout=True) + fig.suptitle(title, fontsize=16, fontweight="bold") + + draw_axis(axes[0], grouped, categories, "duration_s", "Prove time (s)") + draw_axis(axes[1], grouped, categories, "memory_mb", "Process peak memory (MB)") + + labels = [f"{device}\n{function}" for device, function in categories] + axes[1].set_xticks(range(len(categories)), labels, rotation=35, ha="right") + + handles = [ + plt.Line2D([0], [0], marker="o", color="w", label=label, markerfacecolor=color, markersize=8) + for label, color in FUNCTION_COLORS.items() + if any(point["function"] == label for point in points) + ] + axes[0].legend(handles=handles, loc="upper right", frameon=False) + + for ax in axes: + for spine in ("top", "right"): + ax.spines[spine].set_visible(False) + + fig.savefig(output_dir / f"{platform}-sina.svg", format="svg") + plt.close(fig) + + render_platform("ios", "iPhone ProveKit benchmark distributions") + render_platform("android", "Android ProveKit benchmark distributions") + PY + + - name: Publish plot assets + id: publish_plots + shell: bash + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + ASSET_BRANCH: mobench-plots + run: | + set -euo pipefail + + if ! find rendered -type f -path "*/plots/*.svg" | grep -q .; then + echo "base_url=" >> "$GITHUB_OUTPUT" + exit 0 + fi + + remote="https://x-access-token:${GH_TOKEN}@github.com/${REPO}.git" + asset_path="runs/${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" + publish_root="$(mktemp -d)" + + if git clone --quiet --branch "${ASSET_BRANCH}" "${remote}" "${publish_root}" 2>/dev/null; then + : + else + git clone --quiet "${remote}" "${publish_root}" + git -C "${publish_root}" checkout --orphan "${ASSET_BRANCH}" + git -C "${publish_root}" rm -rf . >/dev/null 2>&1 || true + fi + + git -C "${publish_root}" config user.name "github-actions[bot]" + git -C "${publish_root}" config user.email "41898282+github-actions[bot]@users.noreply.github.com" + mkdir -p "${publish_root}/${asset_path}" + + for platform in ios android; do + if [ -d "rendered/${platform}/plots" ]; then + mkdir -p "${publish_root}/${asset_path}/${platform}" + rm -rf "${publish_root}/${asset_path}/${platform}/plots" + cp -R "rendered/${platform}/plots" "${publish_root}/${asset_path}/${platform}/plots" + fi + done + + git -C "${publish_root}" add "${asset_path}" + if ! git -C "${publish_root}" diff --cached --quiet; then + git -C "${publish_root}" commit -m "mobench plots for run ${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" >/dev/null + git -C "${publish_root}" push origin "${ASSET_BRANCH}" >/dev/null + fi + + echo "base_url=https://raw.githubusercontent.com/${REPO}/${ASSET_BRANCH}/${asset_path}" >> "$GITHUB_OUTPUT" + + - name: Rewrite platform summaries for GitHub markdown + shell: bash + env: + PLOT_BASE_URL: ${{ steps.publish_plots.outputs.base_url }} + run: | + set -euo pipefail + + rewrite_platform_summary() { + local platform="$1" + local input="rendered/${platform}/summary.md" + local output="rendered/${platform}/github-summary.md" + if [ ! -f "${input}" ]; then + return 0 + fi + + cp "${input}" "${output}" + if [ -n "${PLOT_BASE_URL:-}" ] && [ -d "rendered/${platform}/plots" ]; then + sed -i "s#](plots/#](${PLOT_BASE_URL}/${platform}/plots/#g" "${output}" + { + echo "" + echo "### ${platform^} Sina Plot" + echo "" + echo "![${platform} Sina plot](${PLOT_BASE_URL}/${platform}/plots/${platform}-sina.svg)" + } >> "${output}" + fi + } + + rewrite_platform_summary ios + rewrite_platform_summary android + + - name: Post sticky PR comment + if: inputs.pr_number != '' && steps.render_summaries.outputs.rendered_count != '0' + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ inputs.pr_number }} + REPO: ${{ inputs.report_repository != '' && inputs.report_repository || github.repository }} + run: | + set -euo pipefail + MARKER="" + BODY="${MARKER} + ## Mobench Benchmark Results + + " + + for platform in ios android; do + PLATFORM_MD_FILE="rendered/${platform}/github-summary.md" + if [ -f "${PLATFORM_MD_FILE}" ]; then + PLATFORM_MD=$(cat "${PLATFORM_MD_FILE}") + BODY="${BODY}${PLATFORM_MD} + + " + fi + done + + BODY="${BODY} + --- + *Posted by [mobench](https://github.com/worldcoin/mobile-bench-rs) at $(date -u '+%Y-%m-%d %H:%M UTC')*" + + comments_json="$(mktemp)" + if gh api "repos/${REPO}/issues/${PR_NUMBER}/comments" > "${comments_json}"; then + EXISTING_COMMENT_ID=$(jq -r --arg marker "${MARKER}" '.[] | select(.body | contains($marker)) | .id' "${comments_json}" | head -1) + else + echo "::warning::Unable to list comments for ${REPO}#${PR_NUMBER}; skipping sticky benchmark comment." + exit 0 + fi + + if [ -n "$EXISTING_COMMENT_ID" ]; then + gh api "repos/${REPO}/issues/comments/${EXISTING_COMMENT_ID}" \ + -X PATCH \ + -f body="${BODY}" \ + --silent || echo "::warning::Unable to update sticky benchmark comment ${EXISTING_COMMENT_ID} in ${REPO}." + else + gh api "repos/${REPO}/issues/${PR_NUMBER}/comments" \ + -f body="${BODY}" \ + --silent || echo "::warning::Unable to create sticky benchmark comment in ${REPO}#${PR_NUMBER}." + fi diff --git a/.github/workflows/mobile-bench.yml b/.github/workflows/mobile-bench.yml new file mode 100644 index 000000000..a2eb8aa3d --- /dev/null +++ b/.github/workflows/mobile-bench.yml @@ -0,0 +1,148 @@ +name: Mobile Benchmarks + +on: + workflow_dispatch: + inputs: + crate_path: + description: "Path to the benchmark crate" + required: false + type: string + default: "./bench-mobile" + functions: + description: "JSON array of benchmark functions" + required: false + type: string + default: '["bench_mobile::bench_passport_fragmented_age_check_prove","bench_mobile::bench_oprf_prove","bench_mobile::bench_passport_complete_age_check_prove"]' + functions_ios: + description: "Optional iOS-specific benchmark functions" + required: false + type: string + default: '["bench_mobile::bench_passport_complete_age_check_prove","bench_mobile::bench_passport_fragmented_age_check_prove","bench_mobile::bench_oprf_prove"]' + functions_android: + description: "Optional Android-specific benchmark functions" + required: false + type: string + default: '["bench_mobile::bench_passport_complete_age_check_prove","bench_mobile::bench_passport_fragmented_age_check_prove","bench_mobile::bench_oprf_prove"]' + platform: + description: "android | ios | both" + required: false + type: choice + default: both + options: + - android + - ios + - both + device_profile: + description: "Device profile to run" + required: false + type: choice + default: "triad" + options: + - smoke + - triad + - worst + device_profile_ios: + description: "Optional iOS-specific device profile" + required: false + type: string + default: "" + device_profile_android: + description: "Optional Android-specific device profile" + required: false + type: string + default: "" + iterations: + description: "Number of benchmark iterations" + required: false + type: string + default: "2" + warmup: + description: "Number of warmup iterations" + required: false + type: string + default: "1" + mobench_version: + description: "Mobench release version to install when mobench_ref is empty" + required: false + type: string + default: "0.1.41" + mobench_ref: + description: "Optional mobile-bench-rs Git ref to install instead of a released version" + required: false + type: string + default: "8c3f002ae515afaa7440107f4e671f7067d276e3" + pr_number: + description: "PR number for reporting" + required: false + type: string + default: "" + report_repository: + description: "owner/repo to receive the sticky benchmark comment; defaults to this repository" + required: false + type: string + default: "" + head_sha: + description: "Exact commit SHA to benchmark" + required: false + type: string + default: "" + requested_by: + description: "Who triggered the run" + required: false + type: string + default: "" + +permissions: + contents: write + actions: read + pull-requests: write + issues: write + +concurrency: + group: mobench-${{ inputs.pr_number != '' && inputs.pr_number || github.run_id }} + cancel-in-progress: false + +jobs: + browserstack-preflight: + name: BrowserStack preflight + runs-on: ubuntu-latest + environment: Browserstack + outputs: + available: ${{ steps.check.outputs.available }} + env: + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + steps: + - name: Check BrowserStack secrets + id: check + shell: bash + run: | + if [ -n "$BROWSERSTACK_USERNAME" ] && [ -n "$BROWSERSTACK_ACCESS_KEY" ]; then + echo "available=true" >> "$GITHUB_OUTPUT" + else + echo "available=false" >> "$GITHUB_OUTPUT" + fi + + browserstack: + name: BrowserStack benchmarks + needs: browserstack-preflight + if: ${{ needs.browserstack-preflight.outputs.available == 'true' }} + uses: ./.github/workflows/mobile-bench-reusable.yml + secrets: inherit + with: + crate_path: ${{ inputs.crate_path }} + functions: ${{ inputs.functions }} + functions_ios: ${{ inputs.functions_ios }} + functions_android: ${{ inputs.functions_android }} + platform: ${{ inputs.platform }} + device_profile: ${{ inputs.device_profile }} + device_profile_ios: ${{ inputs.device_profile_ios }} + device_profile_android: ${{ inputs.device_profile_android }} + iterations: ${{ inputs.iterations }} + warmup: ${{ inputs.warmup }} + mobench_version: ${{ inputs.mobench_version }} + mobench_ref: ${{ inputs.mobench_ref }} + pr_number: ${{ inputs.pr_number }} + report_repository: ${{ inputs.report_repository }} + head_sha: ${{ inputs.head_sha }} + requested_by: ${{ inputs.requested_by }} diff --git a/Cargo.lock b/Cargo.lock index 98578f7d5..e288ef8bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -61,9 +61,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-rlp" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93e50f64a77ad9c5470bf2ad0ca02f228da70c792a8f06634801e202579f35e" +checksum = "dc90b1e703d3c03f4ff7f48e82dd0bc1c8211ab7d079cd836a06fcfeb06651cb" dependencies = [ "arrayvec", "bytes", @@ -417,7 +417,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -427,7 +427,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -437,7 +437,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.8.6", ] [[package]] @@ -457,9 +457,9 @@ dependencies = [ [[package]] name = "async-lsp" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa3be9cb3e4959184a598f9874641d297fb45f09940ac0e8326574a7cb81940" +checksum = "c8b8fd1e175c5ee3108095da452e816519de618beccea23aa053c00cf5e344b9" dependencies = [ "futures", "lsp-types 0.95.1", @@ -514,15 +514,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "axum" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" dependencies = [ "axum-core", "bytes", @@ -603,6 +603,21 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bench-mobile" +version = "0.1.0" +dependencies = [ + "anyhow", + "inventory", + "mobench-sdk", + "provekit-common", + "provekit-ffi", + "rayon", + "serde", + "serde_json", + "thiserror 1.0.69", +] + [[package]] name = "binary-merge" version = "0.1.2" @@ -652,9 +667,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[package]] name = "bitmaps" @@ -731,6 +746,15 @@ dependencies = [ "serde", ] +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + [[package]] name = "bstr" version = "1.12.1" @@ -754,9 +778,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "byte-slice-cast" @@ -784,9 +808,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cc" -version = "1.2.57" +version = "1.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" dependencies = [ "find-msvc-tools", "jobserver", @@ -855,9 +879,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -878,18 +902,18 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.6.0" +version = "4.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19c9f1dde76b736e3681f28cec9d5a61299cbaae0fce80a68e43724ad56031eb" +checksum = "e0a7a9bfdb35811f9e59832f0f05975114d2251b415fb534108e6f34060fd772" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -1024,11 +1048,12 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const_format" -version = "0.2.35" +version = "0.2.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" dependencies = [ "const_format_proc_macros", + "konst", ] [[package]] @@ -1425,9 +1450,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "elliptic-curve" @@ -1540,9 +1565,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "fastrlp" @@ -1607,13 +1632,12 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.27" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" dependencies = [ "cfg-if", "libc", - "libredox 0.1.15", ] [[package]] @@ -1629,7 +1653,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ "byteorder", - "rand 0.8.5", + "rand 0.8.6", "rustc-hex", "static_assertions", ] @@ -1888,9 +1912,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" dependencies = [ "atomic-waker", "bytes", @@ -1898,7 +1922,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.13.0", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -1943,9 +1967,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heapless" @@ -2047,9 +2071,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", @@ -2062,7 +2086,6 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -2070,16 +2093,15 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http", "hyper", "hyper-util", "log", "rustls", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", @@ -2152,12 +2174,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -2165,9 +2188,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -2178,9 +2201,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -2192,15 +2215,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -2212,15 +2235,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -2256,9 +2279,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -2318,12 +2341,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "serde", "serde_core", ] @@ -2358,20 +2381,19 @@ dependencies = [ ] [[package]] -name = "ipnet" -version = "2.12.0" +name = "inventory" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" +dependencies = [ + "rustversion", +] [[package]] -name = "iri-string" -version = "0.7.11" +name = "ipnet" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e7418f59cc01c88316161279a7f665217ae316b388e58a0d10e29f54f1e5eb" -dependencies = [ - "memchr", - "serde", -] +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "is-terminal" @@ -2593,6 +2615,21 @@ 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 = "kqueue" version = "1.1.1" @@ -2605,11 +2642,11 @@ dependencies = [ [[package]] name = "kqueue-sys" -version = "1.0.4" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.11.1", "libc", ] @@ -2633,9 +2670,9 @@ checksum = "82903360c009b816f5ab72a9b68158c27c301ee2c3f20655b55c5e589e7d3bb7" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libm" @@ -2649,21 +2686,18 @@ version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "libc", "redox_syscall 0.4.1", ] [[package]] name = "libredox" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ - "bitflags 2.11.0", "libc", - "plain", - "redox_syscall 0.7.4", ] [[package]] @@ -2680,9 +2714,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "lock_api" @@ -2818,15 +2852,40 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "wasi", "windows-sys 0.61.2", ] +[[package]] +name = "mobench-macros" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e59029c11aea007fbf9fcc64f39ea03776e17ebf32b4b94129c0921130bbb1b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "mobench-sdk" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f685667359ee654be451316bc0074827ac2817fbca11260e2922c742b51d52df" +dependencies = [ + "inventory", + "libc", + "mobench-macros", + "serde", + "serde_json", + "thiserror 1.0.69", +] + [[package]] name = "multimap" version = "0.10.1" @@ -2878,7 +2937,7 @@ version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "crossbeam-channel", "filetime", "fsevent-sys", @@ -2926,9 +2985,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" @@ -3000,15 +3059,14 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "openssl" -version = "0.10.76" +version = "0.10.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "cfg-if", "foreign-types", "libc", - "once_cell", "openssl-macros", "openssl-sys", ] @@ -3032,9 +3090,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.112" +version = "0.9.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" dependencies = [ "cc", "libc", @@ -3044,12 +3102,12 @@ dependencies = [ [[package]] name = "ordered-float" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0218004a4aae742209bee9c3cef05672f6b2708be36a50add8eb613b1f2a4008" +checksum = "b7d950ca161dc355eaf28f82b11345ed76c6e1f6eb1f4f4479e0323b9e2fbd0e" dependencies = [ "num-traits", - "rand 0.8.5", + "rand 0.8.6", "serde", ] @@ -3073,9 +3131,9 @@ dependencies = [ [[package]] name = "p3-challenger" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20e42ba74a49c08c6e99f74cd9b343bfa31aa5721fea55079b18e3fd65f1dcbc" +checksum = "a7d2d45f5a51dc3f965e8d6da60a6c26c807e88657863d56da275eaa05ad36f1" dependencies = [ "p3-field", "p3-maybe-rayon", @@ -3087,9 +3145,9 @@ dependencies = [ [[package]] name = "p3-dft" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e63fa5eb1bd12a240089e72ae3fe10350944d9c166d00a3bfd2a1794db65cf5c" +checksum = "beabb40bc8ac7f5f95870f271fb844c7e2e1ebb7f0761a8eebb2614b56c6b1c1" dependencies = [ "itertools 0.14.0", "p3-field", @@ -3102,74 +3160,73 @@ dependencies = [ [[package]] name = "p3-field" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ebfdb6ef992ae64e9e8f449ac46516ffa584f11afbdf9ee244288c2a633cdf4" +checksum = "4819a3e4c1882431a63d4847ffa10d110017aee4cb9cf4319ca6dca191930969" dependencies = [ "itertools 0.14.0", "num-bigint", "p3-maybe-rayon", "p3-util", "paste", - "rand 0.9.2", + "rand 0.9.4", "serde", "tracing", ] [[package]] name = "p3-koala-bear" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5113f50002c56006685b7d7ae12db568150aa1d4bfb092b883d64ece20138042" +checksum = "cfb02789fca0950e246123d652bd78e75a76e3b90a651fd88dbb215cd3e81f5a" dependencies = [ "p3-challenger", "p3-field", "p3-monty-31", "p3-poseidon2", "p3-symmetric", - "rand 0.9.2", + "rand 0.9.4", ] [[package]] name = "p3-matrix" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5542f96504dae8100c91398fb1e3f5ec669eb9c73d9e0b018a93b5fe32bad230" +checksum = "e6fde449bd2963d394284ec46db8c647e6a5602d90601117b76752072ab54168" dependencies = [ "itertools 0.14.0", "p3-field", "p3-maybe-rayon", "p3-util", - "rand 0.9.2", + "rand 0.9.4", "serde", "tracing", - "transpose", ] [[package]] name = "p3-maybe-rayon" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e5669ca75645f99cd001e9d0289a4eeff2bc2cd9dc3c6c3aaf22643966e83df" +checksum = "54afab3883d8a14676b492709d6c4e9fa535c36718b737db0817aacfaaaa11f6" [[package]] name = "p3-mds" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038763af23df9da653065867fd85b38626079031576c86fd537097e5be6a0da0" +checksum = "3895055d735ac96d010747b3aaabd4c2645b9fd80226960550318db2e25afb75" dependencies = [ "p3-dft", "p3-field", "p3-symmetric", "p3-util", - "rand 0.9.2", + "rand 0.9.4", ] [[package]] name = "p3-monty-31" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a981d60da3d8cbf8561014e2c186068578405fd69098fa75b43d4afb364a47" +checksum = "c9fe0be661891af1f703ceaf57334fcbd540804988984dc2b500dd99740e7c81" dependencies = [ "itertools 0.14.0", "num-bigint", @@ -3182,31 +3239,30 @@ dependencies = [ "p3-symmetric", "p3-util", "paste", - "rand 0.9.2", + "rand 0.9.4", "serde", "spin 0.10.0", "tracing", - "transpose", ] [[package]] name = "p3-poseidon2" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "903b73e4f9a7781a18561c74dc169cf03333497b57a8dd02aaeb130c0f386599" +checksum = "2c6fc2368447576283f8b3849a36095017f25addf06eab9e33b0ce7f96b0b99d" dependencies = [ "p3-field", "p3-mds", "p3-symmetric", "p3-util", - "rand 0.9.2", + "rand 0.9.4", ] [[package]] name = "p3-symmetric" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cd788f04e86dd5c35dd87cad29eefdb6371d2fd5f7664451382eeacae3c3ed0" +checksum = "a14456a42a7d9e65f13999706f1bca2832175935169b3a54286e18331cf1d82f" dependencies = [ "itertools 0.14.0", "p3-field", @@ -3215,11 +3271,12 @@ dependencies = [ [[package]] name = "p3-util" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "663b16021930bc600ecada915c6c3965730a3b9d6a6c23434ccf70bfc29d6881" +checksum = "911154accf66034b0eec4452956c088f92a200b37a8225c1caed74cfbd38cc8d" dependencies = [ "serde", + "transpose", ] [[package]] @@ -3311,7 +3368,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", - "indexmap 2.13.0", + "indexmap 2.14.0", ] [[package]] @@ -3322,24 +3379,24 @@ checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset", "hashbrown 0.15.5", - "indexmap 2.13.0", + "indexmap 2.14.0", "serde", ] [[package]] name = "pin-project" -version = "1.1.11" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.11" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", @@ -3352,12 +3409,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "pkcs8" version = "0.10.2" @@ -3370,15 +3421,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "plain" -version = "0.2.3" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "postcard" @@ -3395,9 +3440,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -3467,7 +3512,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit 0.25.8+spec-1.1.0", + "toml_edit 0.25.11+spec-1.1.0", ] [[package]] @@ -3487,9 +3532,9 @@ checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.11.0", + "bitflags 2.11.1", "num-traits", - "rand 0.9.2", + "rand 0.9.4", "rand_chacha 0.9.0", "rand_xorshift 0.4.0", "regex-syntax", @@ -3614,7 +3659,7 @@ checksum = "95067976aca6421a523e491fce939a3e65249bac4b977adee0ee9771568e8aa3" [[package]] name = "provekit-bench" -version = "0.1.0" +version = "1.0.0" dependencies = [ "anyhow", "divan", @@ -3633,7 +3678,7 @@ dependencies = [ [[package]] name = "provekit-cli" -version = "0.1.5" +version = "1.0.0" dependencies = [ "anyhow", "argh", @@ -3662,7 +3707,7 @@ dependencies = [ [[package]] name = "provekit-common" -version = "0.1.4" +version = "1.0.0" dependencies = [ "anyhow", "ark-bn254", @@ -3695,7 +3740,7 @@ dependencies = [ [[package]] name = "provekit-ffi" -version = "0.1.0" +version = "1.0.0" dependencies = [ "anyhow", "libc", @@ -3705,12 +3750,14 @@ dependencies = [ "provekit-r1cs-compiler", "provekit-verifier", "provekit_noirc_abi", + "provekit_noirc_artifacts", "serde", + "serde_json", ] [[package]] name = "provekit-gnark" -version = "0.1.2" +version = "1.0.0" dependencies = [ "ark-poly", "provekit-common", @@ -3722,7 +3769,7 @@ dependencies = [ [[package]] name = "provekit-prover" -version = "0.1.4" +version = "1.0.0" dependencies = [ "anyhow", "ark-ff 0.5.0", @@ -3741,7 +3788,7 @@ dependencies = [ [[package]] name = "provekit-r1cs-compiler" -version = "0.1.4" +version = "1.0.0" dependencies = [ "anyhow", "ark-bn254", @@ -3770,7 +3817,7 @@ dependencies = [ "digest 0.10.7", "keccak", "p3-koala-bear", - "rand 0.8.5", + "rand 0.8.6", "sha2", "sha3", "zeroize", @@ -3786,13 +3833,13 @@ dependencies = [ "bytemuck", "keccak", "provekit-spongefish", - "rand 0.8.5", + "rand 0.8.6", "rayon", ] [[package]] name = "provekit-verifier" -version = "0.1.4" +version = "1.0.0" dependencies = [ "anyhow", "ark-std 0.5.0", @@ -3867,7 +3914,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0386544eb81ca8263bd7b5d79eb9a5294d5a63855551dfaf75c31f12ff16c0e3" dependencies = [ "fxhash", - "indexmap 2.13.0", + "indexmap 2.14.0", "provekit_acir", "provekit_acvm_blackbox_solver", "provekit_brillig_vm", @@ -3958,7 +4005,7 @@ dependencies = [ "provekit_noirc_errors", "provekit_noirc_frontend", "provekit_noirc_printable_type", - "rand 0.8.5", + "rand 0.8.6", "rayon", "serde", "serde_json", @@ -4061,7 +4108,7 @@ dependencies = [ "provekit_nargo", "provekit_noirc_driver", "provekit_noirc_frontend", - "semver 1.0.27", + "semver 1.0.28", "serde", "thiserror 1.0.69", "toml 0.7.8", @@ -4119,7 +4166,7 @@ dependencies = [ "provekit_noirc_errors", "provekit_noirc_evaluator", "provekit_noirc_frontend", - "rand 0.8.5", + "rand 0.8.6", "regex", "strum", ] @@ -4159,7 +4206,7 @@ dependencies = [ "provekit_fm", "provekit_noirc_abi", "provekit_noirc_artifacts", - "rand 0.8.5", + "rand 0.8.6", "rand_xorshift 0.3.0", "rayon", "sha256", @@ -4417,9 +4464,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -4429,9 +4476,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", @@ -4511,9 +4558,9 @@ checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68" [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -4544,16 +4591,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", -] - -[[package]] -name = "redox_syscall" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" -dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -4569,7 +4607,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom 0.2.17", - "libredox 0.1.15", + "libredox 0.1.16", "thiserror 1.0.69", ] @@ -4723,9 +4761,9 @@ dependencies = [ [[package]] name = "ruint" -version = "1.17.2" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c141e807189ad38a07276942c6623032d3753c8859c146104ac2e4d68865945a" +checksum = "0298da754d1395046b0afdc2f20ee76d29a8ae310cd30ffa84ed42acba9cb12a" dependencies = [ "alloy-rlp", "ark-ff 0.3.0", @@ -4740,8 +4778,8 @@ dependencies = [ "parity-scale-codec", "primitive-types", "proptest", - "rand 0.8.5", - "rand 0.9.2", + "rand 0.8.6", + "rand 0.9.4", "rlp", "ruint-macro", "serde_core", @@ -4797,9 +4835,9 @@ checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc-hex" @@ -4822,7 +4860,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.27", + "semver 1.0.28", ] [[package]] @@ -4831,7 +4869,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "errno", "libc", "linux-raw-sys 0.4.15", @@ -4844,7 +4882,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "errno", "libc", "linux-raw-sys 0.12.1", @@ -4853,9 +4891,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "log", "once_cell", @@ -4880,9 +4918,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "zeroize", ] @@ -4916,9 +4954,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.10" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "ring", "rustls-pki-types", @@ -5092,7 +5130,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -5120,9 +5158,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "semver-parser" @@ -5174,9 +5212,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -5230,15 +5268,16 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.18.0" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f" +checksum = "e72c1c2cb7b223fafb600a619537a871c2818583d619401b785e7c0b746ccde2" dependencies = [ "base64", + "bs58", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.13.0", + "indexmap 2.14.0", "schemars 0.9.0", "schemars 1.2.1", "serde_core", @@ -5249,9 +5288,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.18.0" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65" +checksum = "b90c488738ecb4fb0262f41f43bc40efc5868d9fb744319ddf5f5317f417bfac" dependencies = [ "darling", "proc-macro2", @@ -5294,9 +5333,9 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" dependencies = [ "digest 0.10.7", "keccak", @@ -5345,9 +5384,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "similar" @@ -5411,9 +5450,9 @@ checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "smol_str" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f7a918bd2a9951d18ee6e48f076843e8e73a9a5d22cf05bcd4b7a81bdd04e17" +checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523" dependencies = [ "borsh", "serde_core", @@ -5518,6 +5557,12 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + [[package]] name = "syn" version = "1.0.109" @@ -5566,7 +5611,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -5788,23 +5833,38 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", ] +[[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 = "tokio" -version = "1.50.0" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", - "mio 1.1.1", + "mio 1.2.0", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -5815,9 +5875,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -5893,9 +5953,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "1.1.0+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] @@ -5906,7 +5966,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.14.0", "serde", "serde_spanned", "toml_datetime 0.6.11", @@ -5919,7 +5979,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.13.0", + "indexmap 2.14.0", "serde", "serde_spanned", "toml_datetime 0.6.11", @@ -5929,23 +5989,23 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.8+spec-1.1.0" +version = "0.25.11+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16bff38f1d86c47f9ff0647e6838d7bb362522bdf44006c7068c2b1e606f1f3c" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" dependencies = [ - "indexmap 2.13.0", - "toml_datetime 1.1.0+spec-1.1.0", + "indexmap 2.14.0", + "toml_datetime 1.1.1+spec-1.1.0", "toml_parser", - "winnow 1.0.0", + "winnow 1.0.3", ] [[package]] name = "toml_parser" -version = "1.1.0+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 1.0.0", + "winnow 1.0.3", ] [[package]] @@ -5972,22 +6032,22 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "bytes", "futures-util", "http", "http-body", - "iri-string", "pin-project-lite", "tokio", "tower", "tower-layer", "tower-service", "tracing", + "url", ] [[package]] @@ -6016,11 +6076,12 @@ dependencies = [ [[package]] name = "tracing-appender" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" dependencies = [ "crossbeam-channel", + "symlink", "thiserror 2.0.18", "time", "tracing-subscriber", @@ -6158,9 +6219,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "ucd-trie" @@ -6282,7 +6343,7 @@ dependencies = [ [[package]] name = "verifier-server" -version = "0.1.0" +version = "1.0.0" dependencies = [ "anyhow", "axum", @@ -6350,11 +6411,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -6363,7 +6424,7 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] @@ -6456,7 +6517,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap 2.13.0", + "indexmap 2.14.0", "wasm-encoder", "wasmparser", ] @@ -6467,10 +6528,10 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "hashbrown 0.15.5", - "indexmap 2.13.0", - "semver 1.0.27", + "indexmap 2.14.0", + "semver 1.0.28", ] [[package]] @@ -6489,14 +6550,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" dependencies = [ - "webpki-root-certs 1.0.6", + "webpki-root-certs 1.0.7", ] [[package]] name = "webpki-root-certs" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" dependencies = [ "rustls-pki-types", ] @@ -6919,9 +6980,9 @@ dependencies = [ [[package]] name = "winnow" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" dependencies = [ "memchr", ] @@ -6935,6 +6996,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -6954,7 +7021,7 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck 0.5.0", - "indexmap 2.13.0", + "indexmap 2.14.0", "prettyplease", "syn 2.0.117", "wasm-metadata", @@ -6984,8 +7051,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.0", - "indexmap 2.13.0", + "bitflags 2.11.1", + "indexmap 2.14.0", "log", "serde", "serde_derive", @@ -7004,9 +7071,9 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap 2.13.0", + "indexmap 2.14.0", "log", - "semver 1.0.27", + "semver 1.0.28", "serde", "serde_derive", "serde_json", @@ -7016,9 +7083,9 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "wyz" @@ -7040,9 +7107,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -7051,9 +7118,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -7063,18 +7130,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.47" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", @@ -7083,18 +7150,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -7124,9 +7191,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -7135,9 +7202,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -7146,9 +7213,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index f477dd23d..cabb8a456 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] resolver = "2" members = [ + "bench-mobile", # "skyscraper/fp-rounding", # "skyscraper/hla", # "skyscraper/bn254-multiplier", @@ -119,6 +120,8 @@ chrono = "0.4.41" divan = "0.1.21" hex = "0.4.3" itertools = "0.14.0" +inventory = "0.3" +mobench-sdk = { version = "0.1.41", default-features = false, features = ["registry"] } paste = "1.0.15" postcard = { version = "1.1.1", features = ["use-std"] } primitive-types = "0.13.1" @@ -147,6 +150,7 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter", "ansi"] } tracing-tracy = "=0.11.4" tracy-client = "=0.18.0" tracy-client-sys = "=0.24.3" +uniffi = "0.28" parking_lot = "0.12" xz2 = "0.1.7" zerocopy = "0.8.25" diff --git a/bench-mobile/Cargo.toml b/bench-mobile/Cargo.toml new file mode 100644 index 000000000..e993d2c3b --- /dev/null +++ b/bench-mobile/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "bench-mobile" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +publish = false +description = "Mobile benchmarks for ProveKit Noir passport proving" + +[package.metadata.cargo-machete] +ignored = ["inventory"] + +[lib] +crate-type = ["lib", "cdylib", "staticlib"] + +[dependencies] +anyhow.workspace = true +inventory.workspace = true +mobench-sdk.workspace = true +provekit-ffi.workspace = true +provekit-common.workspace = true +rayon.workspace = true +serde.workspace = true +serde_json.workspace = true +thiserror = "1.0" + +[lints] +workspace = true diff --git a/bench-mobile/build.rs b/bench-mobile/build.rs new file mode 100644 index 000000000..dd8cbcd39 --- /dev/null +++ b/bench-mobile/build.rs @@ -0,0 +1,104 @@ +use std::{ + env, fs, io, + path::{Path, PathBuf}, +}; + +struct FixtureArtifact { + output_file: &'static str, + source_target_rel: &'static str, +} + +const FIXTURE_ARTIFACTS: &[FixtureArtifact] = &[ + FixtureArtifact { + output_file: "complete_age_check.json", + source_target_rel: "noir-examples/noir-passport-monolithic/complete_age_check/target/\ + complete_age_check.json", + }, + FixtureArtifact { + output_file: "t_add_dsc_720.json", + source_target_rel: "noir-examples/noir-passport/merkle_age_check/target/t_add_dsc_720.json", + }, + FixtureArtifact { + output_file: "t_add_id_data_720.json", + source_target_rel: "noir-examples/noir-passport/merkle_age_check/target/t_add_id_data_720.\ + json", + }, + FixtureArtifact { + output_file: "t_add_integrity_commit.json", + source_target_rel: "noir-examples/noir-passport/merkle_age_check/target/\ + t_add_integrity_commit.json", + }, + FixtureArtifact { + output_file: "t_attest.json", + source_target_rel: "noir-examples/noir-passport/merkle_age_check/target/t_attest.json", + }, + FixtureArtifact { + output_file: "oprf.json", + source_target_rel: "noir-examples/oprf/target/oprf.json", + }, + FixtureArtifact { + output_file: "p256.json", + source_target_rel: "noir-examples/p256_bigcurve/target/p256.json", + }, +]; + +fn copy_if_present(from: &Path, to: &Path) -> io::Result { + if from.exists() { + fs::copy(from, to)?; + Ok(true) + } else { + Ok(false) + } +} + +fn main() { + let manifest_dir = + PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR")); + let workspace_dir = manifest_dir + .parent() + .expect("bench-mobile crate should live at workspace root") + .to_path_buf(); + let out_dir = + PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR")).join("bench_mobile_fixtures"); + let artifact_dir = env::var_os("PROVEKIT_MOBILE_BENCH_ARTIFACT_DIR").map(PathBuf::from); + let require_artifacts = env::var_os("PROVEKIT_REQUIRE_MOBILE_BENCH_ARTIFACTS") + .is_some_and(|value| value != "0" && value != "false"); + + fs::create_dir_all(&out_dir).expect("create generated fixture output dir"); + + for artifact in FIXTURE_ARTIFACTS { + let out_path = out_dir.join(artifact.output_file); + let mut copied = false; + + if let Some(dir) = artifact_dir.as_ref() { + copied = copy_if_present(&dir.join(artifact.output_file), &out_path) + .expect("copy mobile benchmark artifact from override dir"); + println!("cargo:rerun-if-env-changed=PROVEKIT_MOBILE_BENCH_ARTIFACT_DIR"); + } + + if !copied { + let source_path = workspace_dir.join(artifact.source_target_rel); + copied = copy_if_present(&source_path, &out_path) + .expect("copy mobile benchmark artifact from Noir target dir"); + println!("cargo:rerun-if-changed={}", source_path.display()); + } + + if !copied { + println!( + "cargo:warning=missing generated Noir artifact {}; run the mobile fixture \ + generation workflow step before executing bench-mobile tests", + artifact.output_file + ); + if require_artifacts { + panic!( + "missing required generated Noir artifact {} at {}; run \ + bench-mobile/scripts/generate-fixtures.sh before building mobile benchmark \ + artifacts", + artifact.output_file, + workspace_dir.join(artifact.source_target_rel).display() + ); + } + fs::write(&out_path, "{}\n").expect("write placeholder mobile benchmark artifact"); + } + } +} diff --git a/bench-mobile/scripts/generate-fixtures.sh b/bench-mobile/scripts/generate-fixtures.sh new file mode 100755 index 000000000..aca3b1064 --- /dev/null +++ b/bench-mobile/scripts/generate-fixtures.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" + +compile_fixture() { + local circuit_dir="$1" + local aggregate_target_dir="${2:-}" + echo "Generating Noir artifact in ${circuit_dir}" + ( + cd "${repo_root}/${circuit_dir}" + nargo compile --skip-brillig-constraints-check --force + ) + + if [[ -n "${aggregate_target_dir}" ]]; then + local circuit_target_dir="${repo_root}/${circuit_dir}/target" + local target_dir="${repo_root}/${aggregate_target_dir}" + mkdir -p "${target_dir}" + if compgen -G "${circuit_target_dir}/*.json" >/dev/null; then + cp "${circuit_target_dir}"/*.json "${target_dir}/" + fi + fi +} + +compile_fixture "noir-examples/noir-passport-monolithic/complete_age_check" +compile_fixture "noir-examples/noir-passport/merkle_age_check/t_add_dsc_720" \ + "noir-examples/noir-passport/merkle_age_check/target" +compile_fixture "noir-examples/noir-passport/merkle_age_check/t_add_id_data_720" \ + "noir-examples/noir-passport/merkle_age_check/target" +compile_fixture "noir-examples/noir-passport/merkle_age_check/t_add_integrity_commit" \ + "noir-examples/noir-passport/merkle_age_check/target" +compile_fixture "noir-examples/noir-passport/merkle_age_check/t_attest" \ + "noir-examples/noir-passport/merkle_age_check/target" +compile_fixture "noir-examples/oprf" +compile_fixture "noir-examples/p256_bigcurve" diff --git a/bench-mobile/src/examples.rs b/bench-mobile/src/examples.rs new file mode 100644 index 000000000..35bfa3957 --- /dev/null +++ b/bench-mobile/src/examples.rs @@ -0,0 +1,84 @@ +use { + anyhow::{Context, Result}, + provekit_common::NoirProof, + provekit_ffi::in_process::{ + prepare_noir_program_from_json, PreparedNoirProgram, VerifiedNoirProgram, + }, +}; + +const COMPLETE_AGE_CHECK_PROGRAM: &str = include_str!(concat!( + env!("OUT_DIR"), + "/bench_mobile_fixtures/complete_age_check.json" +)); +const COMPLETE_AGE_CHECK_TOML: &str = + include_str!("../../noir-examples/noir-passport-monolithic/complete_age_check/Prover.toml"); +const OPRF_PROGRAM: &str = + include_str!(concat!(env!("OUT_DIR"), "/bench_mobile_fixtures/oprf.json")); +const OPRF_TOML: &str = include_str!("../../noir-examples/oprf/Prover.toml"); +const P256_BIGCURVE_PROGRAM: &str = + include_str!(concat!(env!("OUT_DIR"), "/bench_mobile_fixtures/p256.json")); +const P256_BIGCURVE_TOML: &str = include_str!("../../noir-examples/p256_bigcurve/Prover.toml"); + +#[derive(Clone, Copy)] +pub enum MobileBenchFixture { + CompleteAgeCheck, + Oprf, + P256Bigcurve, +} + +impl MobileBenchFixture { + fn name(self) -> &'static str { + match self { + Self::CompleteAgeCheck => "complete_age_check", + Self::Oprf => "oprf", + Self::P256Bigcurve => "p256_bigcurve", + } + } + + fn program_json(self) -> &'static str { + match self { + Self::CompleteAgeCheck => COMPLETE_AGE_CHECK_PROGRAM, + Self::Oprf => OPRF_PROGRAM, + Self::P256Bigcurve => P256_BIGCURVE_PROGRAM, + } + } + + fn prover_toml(self) -> &'static str { + match self { + Self::CompleteAgeCheck => COMPLETE_AGE_CHECK_TOML, + Self::Oprf => OPRF_TOML, + Self::P256Bigcurve => P256_BIGCURVE_TOML, + } + } +} + +pub type PreparedCircuitFixture = PreparedNoirProgram; +pub type VerifiedCircuitFixture = VerifiedNoirProgram; + +pub fn prepare_fixture(fixture: MobileBenchFixture) -> Result { + prepare_noir_program_from_json( + fixture.name(), + fixture.program_json(), + fixture.prover_toml(), + ) + .with_context(|| format!("while preparing {} benchmark fixture", fixture.name())) +} + +pub fn prove_fixture(prepared: PreparedCircuitFixture) -> Result { + prepared.prove() +} + +pub fn prove_fixture_proof_only(prepared: PreparedCircuitFixture) -> Result { + prepared.prove_only() +} + +pub fn verify_fixture(verified: VerifiedCircuitFixture) -> Result { + verified.verify() +} + +pub fn fixture_end_to_end_smoke(fixture: MobileBenchFixture) -> Result<()> { + let prepared = prepare_fixture(fixture)?; + let verified = prove_fixture(prepared)?; + let _verified = verify_fixture(verified)?; + Ok(()) +} diff --git a/bench-mobile/src/lib.rs b/bench-mobile/src/lib.rs new file mode 100644 index 000000000..fec5fca57 --- /dev/null +++ b/bench-mobile/src/lib.rs @@ -0,0 +1,529 @@ +//! Mobile benchmarks for ProveKit passport and example circuits. + +use { + crate::passport::{ + prove_complete_age_check_fixture, prove_complete_age_check_fixture_proof_only, + prove_fragmented_age_check_fixture_proof_only, verify_complete_age_check_fixture, + PreparedCompleteAgeCheckFixture, PreparedFragmentedAgeCheckFixture, + VerifiedCompleteAgeCheckFixture, + }, + examples::{MobileBenchFixture, PreparedCircuitFixture, VerifiedCircuitFixture}, + mobench_sdk::{benchmark, profile_phase}, + serde_json::json, + std::hint::black_box, +}; + +pub mod examples; +pub mod passport; + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct BenchSpec { + pub name: String, + pub iterations: u32, + pub warmup: u32, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct BenchSample { + pub duration_ns: u64, + pub cpu_time_ms: Option, + pub peak_memory_kb: Option, + pub process_peak_memory_kb: Option, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct SemanticPhase { + pub name: String, + pub duration_ns: u64, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct HarnessTimelineSpan { + pub phase: String, + pub start_offset_ns: u64, + pub end_offset_ns: u64, + pub iteration: Option, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct BenchReport { + pub spec: BenchSpec, + pub samples: Vec, + pub phases: Vec, + pub timeline: Vec, +} + +#[derive(Debug, thiserror::Error)] +pub enum BenchError { + #[error("iterations must be greater than zero")] + InvalidIterations, + + #[error("unknown benchmark function: {name}")] + UnknownFunction { name: String }, + + #[error("benchmark execution failed: {reason}")] + ExecutionFailed { reason: String }, +} + +#[cfg(target_os = "android")] +fn configure_android_complete_age_check_threads(function: &str) { + use std::sync::Once; + + static INIT: Once = Once::new(); + + if function != "bench_mobile::bench_passport_complete_age_check_prove" { + return; + } + + INIT.call_once(|| { + let threads = std::env::var("PROVEKIT_ANDROID_COMPLETE_AGE_RAYON_THREADS") + .ok() + .and_then(|value| value.parse::().ok()) + .filter(|threads| *threads > 0) + .unwrap_or(1); + + match rayon::ThreadPoolBuilder::new() + .num_threads(threads) + .build_global() + { + Ok(()) => log_benchmark_lifecycle( + "rayon_configured", + function, + 0, + 0, + json!({ "threads": threads }), + ), + Err(error) => log_benchmark_lifecycle( + "rayon_config_skipped", + function, + 0, + 0, + json!({ "threads": threads, "error": error.to_string() }), + ), + } + }); +} + +#[cfg(not(target_os = "android"))] +fn configure_android_complete_age_check_threads(_function: &str) {} + +impl From for BenchSpec { + fn from(spec: mobench_sdk::BenchSpec) -> Self { + Self { + name: spec.name, + iterations: spec.iterations, + warmup: spec.warmup, + } + } +} + +impl From for mobench_sdk::BenchSpec { + fn from(spec: BenchSpec) -> Self { + Self { + name: spec.name, + iterations: spec.iterations, + warmup: spec.warmup, + } + } +} + +impl From for BenchSample { + fn from(sample: mobench_sdk::BenchSample) -> Self { + Self { + duration_ns: sample.duration_ns, + cpu_time_ms: sample.cpu_time_ms, + peak_memory_kb: sample.peak_memory_kb, + process_peak_memory_kb: sample.process_peak_memory_kb, + } + } +} + +impl From for SemanticPhase { + fn from(phase: mobench_sdk::SemanticPhase) -> Self { + Self { + name: phase.name, + duration_ns: phase.duration_ns, + } + } +} + +impl From for HarnessTimelineSpan { + fn from(span: mobench_sdk::HarnessTimelineSpan) -> Self { + Self { + phase: span.phase, + start_offset_ns: span.start_offset_ns, + end_offset_ns: span.end_offset_ns, + iteration: span.iteration, + } + } +} + +impl From for BenchReport { + fn from(report: mobench_sdk::RunnerReport) -> Self { + Self { + spec: report.spec.into(), + samples: report.samples.into_iter().map(Into::into).collect(), + phases: report.phases.into_iter().map(Into::into).collect(), + timeline: report.timeline.into_iter().map(Into::into).collect(), + } + } +} + +impl From for BenchError { + fn from(err: mobench_sdk::BenchError) -> Self { + match err { + mobench_sdk::BenchError::Runner(runner_err) => Self::ExecutionFailed { + reason: runner_err.to_string(), + }, + mobench_sdk::BenchError::UnknownFunction(name, _available) => { + Self::UnknownFunction { name } + } + _ => Self::ExecutionFailed { + reason: err.to_string(), + }, + } + } +} + +fn log_benchmark_lifecycle( + event: &str, + function: &str, + iterations: u32, + warmup: u32, + extra: serde_json::Value, +) { + let payload = json!({ + "tag": "MOBENCH_LIFECYCLE", + "event": event, + "function": function, + "iterations": iterations, + "warmup": warmup, + "extra": extra, + }); + + if event == "error" { + eprintln!("{payload}"); + } else { + println!("{payload}"); + } +} + +pub fn run_benchmark(spec: BenchSpec) -> Result { + let function = spec.name.clone(); + let iterations = spec.iterations; + let warmup = spec.warmup; + configure_android_complete_age_check_threads(&function); + log_benchmark_lifecycle( + "start", + &function, + iterations, + warmup, + json!({ + "resolved_function": function, + }), + ); + + let sdk_spec: mobench_sdk::BenchSpec = spec.into(); + match mobench_sdk::run_benchmark(sdk_spec) { + Ok(report) => { + log_benchmark_lifecycle( + "success", + &report.spec.name, + report.spec.iterations, + report.spec.warmup, + json!({ + "sample_count": report.samples.len(), + "phase_count": report.phases.len(), + "timeline_span_count": report.timeline.len(), + "sample_resource_count": report + .samples + .iter() + .filter(|sample| { + sample.cpu_time_ms.is_some() + || sample.peak_memory_kb.is_some() + || sample.process_peak_memory_kb.is_some() + }) + .count(), + }), + ); + Ok(report.into()) + } + Err(err) => { + log_benchmark_lifecycle( + "error", + &function, + iterations, + warmup, + json!({ + "resolved_function": function, + "error": err.to_string(), + }), + ); + Err(err.into()) + } + } +} + +mobench_sdk::export_native_c_abi!(); + +fn setup_complete_age_check_prepared() -> PreparedCompleteAgeCheckFixture { + passport::prepare_complete_age_check_fixture().expect("prepare complete_age_check fixture") +} + +fn setup_complete_age_check_verified() -> VerifiedCompleteAgeCheckFixture { + let prepared = setup_complete_age_check_prepared(); + prove_complete_age_check_fixture(prepared).expect("prove complete_age_check fixture") +} + +fn setup_fragmented_age_check_prepared() -> PreparedFragmentedAgeCheckFixture { + passport::prepare_fragmented_age_check_fixture().expect("prepare fragmented age_check fixture") +} + +fn setup_oprf_prepared() -> PreparedCircuitFixture { + examples::prepare_fixture(MobileBenchFixture::Oprf).expect("prepare oprf fixture") +} + +fn setup_oprf_verified() -> VerifiedCircuitFixture { + let prepared = setup_oprf_prepared(); + examples::prove_fixture(prepared).expect("prove oprf fixture") +} + +fn setup_p256_bigcurve_prepared() -> PreparedCircuitFixture { + examples::prepare_fixture(MobileBenchFixture::P256Bigcurve) + .expect("prepare p256_bigcurve fixture") +} + +fn setup_p256_bigcurve_verified() -> VerifiedCircuitFixture { + let prepared = setup_p256_bigcurve_prepared(); + examples::prove_fixture(prepared).expect("prove p256_bigcurve fixture") +} + +#[benchmark] +pub fn bench_passport_complete_age_check_prepare() { + let prepared = profile_phase("prepare", || { + passport::prepare_complete_age_check_fixture().expect("prepare complete_age_check fixture") + }); + + black_box(( + prepared.prover_size(), + prepared.constraint_count(), + prepared.input_count(), + )); +} + +#[benchmark(setup = setup_complete_age_check_prepared, per_iteration)] +pub fn bench_passport_complete_age_check_prove(prepared: PreparedCompleteAgeCheckFixture) { + let proof = profile_phase("prove", || { + prove_complete_age_check_fixture_proof_only(prepared) + .expect("prove complete_age_check fixture") + }); + + black_box(proof); +} + +#[benchmark(setup = setup_complete_age_check_verified)] +pub fn bench_passport_complete_age_check_verify(verified: &VerifiedCompleteAgeCheckFixture) { + let verified = profile_phase("verify", || { + verify_complete_age_check_fixture(verified.clone()) + .expect("verify complete_age_check fixture") + }); + + black_box(verified); +} + +#[benchmark] +pub fn bench_passport_complete_age_check_e2e() { + let prepared = profile_phase("prepare", || { + passport::prepare_complete_age_check_fixture().expect("prepare complete_age_check fixture") + }); + let verified = profile_phase("prove", || { + prove_complete_age_check_fixture(prepared).expect("prove complete_age_check fixture") + }); + let verified = profile_phase("verify", || { + verify_complete_age_check_fixture(verified).expect("verify complete_age_check fixture") + }); + + black_box(verified); +} + +#[benchmark] +pub fn bench_passport_fragmented_age_check_prepare() { + let prepared = profile_phase("prepare", || { + passport::prepare_fragmented_age_check_fixture() + .expect("prepare fragmented age_check fixture") + }); + + black_box(( + prepared.add_dsc.prover_size(), + prepared.add_id_data.prover_size(), + prepared.add_integrity_commit.prover_size(), + prepared.attest.prover_size(), + )); +} + +#[benchmark(setup = setup_fragmented_age_check_prepared, per_iteration)] +pub fn bench_passport_fragmented_age_check_prove(prepared: PreparedFragmentedAgeCheckFixture) { + let proofs = profile_phase("prove", || { + prove_fragmented_age_check_fixture_proof_only(prepared) + .expect("prove fragmented age_check fixture") + }); + + black_box(proofs); +} + +#[benchmark] +pub fn bench_oprf_prepare() { + let prepared = profile_phase("prepare", || { + examples::prepare_fixture(MobileBenchFixture::Oprf).expect("prepare oprf fixture") + }); + + black_box(( + prepared.prover_size(), + prepared.constraint_count(), + prepared.input_count(), + )); +} + +#[benchmark(setup = setup_oprf_prepared, per_iteration)] +pub fn bench_oprf_prove(prepared: PreparedCircuitFixture) { + let proof = profile_phase("prove", || { + examples::prove_fixture_proof_only(prepared).expect("prove oprf fixture") + }); + + black_box(proof); +} + +#[benchmark(setup = setup_oprf_verified)] +pub fn bench_oprf_verify(verified: &VerifiedCircuitFixture) { + let verified = profile_phase("verify", || { + examples::verify_fixture(verified.clone()).expect("verify oprf fixture") + }); + + black_box(verified); +} + +#[benchmark] +pub fn bench_oprf_e2e() { + let prepared = profile_phase("prepare", || { + examples::prepare_fixture(MobileBenchFixture::Oprf).expect("prepare oprf fixture") + }); + let verified = profile_phase("prove", || { + examples::prove_fixture(prepared).expect("prove oprf fixture") + }); + let verified = profile_phase("verify", || { + examples::verify_fixture(verified).expect("verify oprf fixture") + }); + + black_box(verified); +} + +#[benchmark] +pub fn bench_p256_bigcurve_prepare() { + let prepared = profile_phase("prepare", || { + examples::prepare_fixture(MobileBenchFixture::P256Bigcurve) + .expect("prepare p256_bigcurve fixture") + }); + + black_box(( + prepared.prover_size(), + prepared.constraint_count(), + prepared.input_count(), + )); +} + +#[benchmark(setup = setup_p256_bigcurve_prepared, per_iteration)] +pub fn bench_p256_bigcurve_prove(prepared: PreparedCircuitFixture) { + let verified = profile_phase("prove", || { + examples::prove_fixture(prepared).expect("prove p256_bigcurve fixture") + }); + + black_box(verified); +} + +#[benchmark(setup = setup_p256_bigcurve_verified)] +pub fn bench_p256_bigcurve_verify(verified: &VerifiedCircuitFixture) { + let verified = profile_phase("verify", || { + examples::verify_fixture(verified.clone()).expect("verify p256_bigcurve fixture") + }); + + black_box(verified); +} + +#[benchmark] +pub fn bench_p256_bigcurve_e2e() { + let prepared = profile_phase("prepare", || { + examples::prepare_fixture(MobileBenchFixture::P256Bigcurve) + .expect("prepare p256_bigcurve fixture") + }); + let verified = profile_phase("prove", || { + examples::prove_fixture(prepared).expect("prove p256_bigcurve fixture") + }); + let verified = profile_phase("verify", || { + examples::verify_fixture(verified).expect("verify p256_bigcurve fixture") + }); + + black_box(verified); +} + +#[cfg(test)] +mod tests { + use super::BenchReport; + + #[test] + fn report_conversion_preserves_sample_resource_metrics() { + let report = mobench_sdk::RunnerReport { + spec: mobench_sdk::BenchSpec { + name: "bench_mobile::bench_passport_complete_age_check_prove".to_string(), + iterations: 1, + warmup: 0, + }, + samples: vec![mobench_sdk::BenchSample { + duration_ns: 123, + cpu_time_ms: Some(7), + peak_memory_kb: Some(48), + process_peak_memory_kb: Some(1024), + }], + phases: vec![], + timeline: vec![], + }; + + let value = + serde_json::to_value(BenchReport::from(report)).expect("serialize bench report"); + + assert_eq!(value["samples"][0]["cpu_time_ms"], 7); + assert_eq!(value["samples"][0]["peak_memory_kb"], 48); + assert_eq!(value["samples"][0]["process_peak_memory_kb"], 1024); + } + + #[test] + fn report_conversion_preserves_timeline_spans() { + let report = mobench_sdk::RunnerReport { + spec: mobench_sdk::BenchSpec { + name: "bench_mobile::bench_passport_complete_age_check_verify".to_string(), + iterations: 1, + warmup: 0, + }, + samples: vec![mobench_sdk::BenchSample { + duration_ns: 321, + cpu_time_ms: None, + peak_memory_kb: None, + process_peak_memory_kb: None, + }], + phases: vec![], + timeline: vec![mobench_sdk::HarnessTimelineSpan { + phase: "measured".to_string(), + start_offset_ns: 10, + end_offset_ns: 20, + iteration: Some(0), + }], + }; + + let value = + serde_json::to_value(BenchReport::from(report)).expect("serialize bench report"); + + assert_eq!(value["timeline"][0]["phase"], "measured"); + assert_eq!(value["timeline"][0]["start_offset_ns"], 10); + assert_eq!(value["timeline"][0]["end_offset_ns"], 20); + assert_eq!(value["timeline"][0]["iteration"], 0); + } +} diff --git a/bench-mobile/src/passport.rs b/bench-mobile/src/passport.rs new file mode 100644 index 000000000..9bf0f9c68 --- /dev/null +++ b/bench-mobile/src/passport.rs @@ -0,0 +1,212 @@ +use { + anyhow::{Context, Result}, + provekit_common::NoirProof, + provekit_ffi::in_process::{ + prepare_noir_program_from_json, trim_process_memory, PreparedNoirProgram, + VerifiedNoirProgram, + }, +}; + +const COMPLETE_AGE_CHECK_PROGRAM: &str = include_str!(concat!( + env!("OUT_DIR"), + "/bench_mobile_fixtures/complete_age_check.json" +)); +const COMPLETE_AGE_CHECK_TOML: &str = + include_str!("../../noir-examples/noir-passport-monolithic/complete_age_check/Prover.toml"); +const FRAGMENTED_ADD_DSC_PROGRAM: &str = include_str!(concat!( + env!("OUT_DIR"), + "/bench_mobile_fixtures/t_add_dsc_720.json" +)); +const FRAGMENTED_ADD_DSC_TOML: &str = include_str!( + "../../noir-examples/noir-passport/merkle_age_check/benchmark-inputs/tbs_720/t_add_dsc_720.\ + toml" +); +const FRAGMENTED_ADD_ID_DATA_PROGRAM: &str = include_str!(concat!( + env!("OUT_DIR"), + "/bench_mobile_fixtures/t_add_id_data_720.json" +)); +const FRAGMENTED_ADD_ID_DATA_TOML: &str = include_str!( + "../../noir-examples/noir-passport/merkle_age_check/benchmark-inputs/tbs_720/\ + t_add_id_data_720.toml" +); +const FRAGMENTED_ADD_INTEGRITY_COMMIT_PROGRAM: &str = include_str!(concat!( + env!("OUT_DIR"), + "/bench_mobile_fixtures/t_add_integrity_commit.json" +)); +const FRAGMENTED_ADD_INTEGRITY_COMMIT_TOML: &str = include_str!( + "../../noir-examples/noir-passport/merkle_age_check/benchmark-inputs/tbs_720/\ + t_add_integrity_commit.toml" +); +const FRAGMENTED_ATTEST_PROGRAM: &str = include_str!(concat!( + env!("OUT_DIR"), + "/bench_mobile_fixtures/t_attest.json" +)); +const FRAGMENTED_ATTEST_TOML: &str = include_str!( + "../../noir-examples/noir-passport/merkle_age_check/benchmark-inputs/tbs_720/t_attest.toml" +); + +pub type PreparedCompleteAgeCheckFixture = PreparedNoirProgram; +pub type VerifiedCompleteAgeCheckFixture = VerifiedNoirProgram; + +/// Prepared prover state for the four-stage fragmented age-check fixture. +#[derive(Clone)] +pub struct PreparedFragmentedAgeCheckFixture { + pub add_dsc: PreparedNoirProgram, + pub add_id_data: PreparedNoirProgram, + pub add_integrity_commit: PreparedNoirProgram, + pub attest: PreparedNoirProgram, +} + +/// Verified proof outputs for the four-stage fragmented age-check fixture. +#[derive(Clone)] +pub struct VerifiedFragmentedAgeCheckFixture { + pub add_dsc: VerifiedNoirProgram, + pub add_id_data: VerifiedNoirProgram, + pub add_integrity_commit: VerifiedNoirProgram, + pub attest: VerifiedNoirProgram, +} + +/// Proof-only outputs for the four-stage fragmented age-check fixture. +#[derive(Clone)] +pub struct FragmentedAgeCheckProofs { + pub add_dsc: NoirProof, + pub add_id_data: NoirProof, + pub add_integrity_commit: NoirProof, + pub attest: NoirProof, +} + +pub fn prepare_complete_age_check_fixture() -> Result { + prepare_noir_program_from_json( + "complete_age_check", + COMPLETE_AGE_CHECK_PROGRAM, + COMPLETE_AGE_CHECK_TOML, + ) + .context("while preparing complete_age_check benchmark fixture") +} + +pub fn prove_complete_age_check_fixture( + prepared: PreparedCompleteAgeCheckFixture, +) -> Result { + let verified = prepared.prove()?; + trim_process_memory(); + Ok(verified) +} + +pub fn prove_complete_age_check_fixture_proof_only( + prepared: PreparedCompleteAgeCheckFixture, +) -> Result { + let proof = prepared.prove_only()?; + trim_process_memory(); + Ok(proof) +} + +pub fn verify_complete_age_check_fixture( + verified: VerifiedCompleteAgeCheckFixture, +) -> Result { + verified.verify() +} + +/// Prepare all four checked-in fragmented age-check stages. +pub fn prepare_fragmented_age_check_fixture() -> Result { + Ok(PreparedFragmentedAgeCheckFixture { + add_dsc: prepare_noir_program_from_json( + "t_add_dsc_720", + FRAGMENTED_ADD_DSC_PROGRAM, + FRAGMENTED_ADD_DSC_TOML, + ) + .context("while preparing t_add_dsc_720 benchmark fixture")?, + add_id_data: prepare_noir_program_from_json( + "t_add_id_data_720", + FRAGMENTED_ADD_ID_DATA_PROGRAM, + FRAGMENTED_ADD_ID_DATA_TOML, + ) + .context("while preparing t_add_id_data_720 benchmark fixture")?, + add_integrity_commit: prepare_noir_program_from_json( + "t_add_integrity_commit", + FRAGMENTED_ADD_INTEGRITY_COMMIT_PROGRAM, + FRAGMENTED_ADD_INTEGRITY_COMMIT_TOML, + ) + .context("while preparing t_add_integrity_commit benchmark fixture")?, + attest: prepare_noir_program_from_json( + "t_attest", + FRAGMENTED_ATTEST_PROGRAM, + FRAGMENTED_ATTEST_TOML, + ) + .context("while preparing t_attest benchmark fixture")?, + }) +} + +/// Prove every fragmented age-check stage once, dropping verifier state before +/// each proof. +pub fn prove_fragmented_age_check_fixture_proof_only( + prepared: PreparedFragmentedAgeCheckFixture, +) -> Result { + let add_dsc = prepared.add_dsc.prove_only()?; + trim_process_memory(); + + let add_id_data = prepared.add_id_data.prove_only()?; + trim_process_memory(); + + let add_integrity_commit = prepared.add_integrity_commit.prove_only()?; + trim_process_memory(); + + let attest = prepared.attest.prove_only()?; + trim_process_memory(); + + Ok(FragmentedAgeCheckProofs { + add_dsc, + add_id_data, + add_integrity_commit, + attest, + }) +} + +/// Prove every fragmented age-check stage once and return the verified outputs. +pub fn prove_fragmented_age_check_fixture( + prepared: PreparedFragmentedAgeCheckFixture, +) -> Result { + let add_dsc = prepared.add_dsc.prove()?; + trim_process_memory(); + + let add_id_data = prepared.add_id_data.prove()?; + trim_process_memory(); + + let add_integrity_commit = prepared.add_integrity_commit.prove()?; + trim_process_memory(); + + let attest = prepared.attest.prove()?; + trim_process_memory(); + + Ok(VerifiedFragmentedAgeCheckFixture { + add_dsc, + add_id_data, + add_integrity_commit, + attest, + }) +} + +/// Verify every fragmented age-check stage proof once. +pub fn verify_fragmented_age_check_fixture( + verified: VerifiedFragmentedAgeCheckFixture, +) -> Result { + Ok(VerifiedFragmentedAgeCheckFixture { + add_dsc: verified.add_dsc.verify()?, + add_id_data: verified.add_id_data.verify()?, + add_integrity_commit: verified.add_integrity_commit.verify()?, + attest: verified.attest.verify()?, + }) +} + +pub fn passport_fragmented_age_check_end_to_end_smoke() -> Result<()> { + let prepared = prepare_fragmented_age_check_fixture()?; + let verified = prove_fragmented_age_check_fixture(prepared)?; + let _verified = verify_fragmented_age_check_fixture(verified)?; + Ok(()) +} + +pub fn passport_complete_age_check_end_to_end_smoke() -> Result<()> { + let prepared = prepare_complete_age_check_fixture()?; + let verified = prove_complete_age_check_fixture(prepared)?; + let _verified = verify_complete_age_check_fixture(verified)?; + Ok(()) +} diff --git a/bench-mobile/tests/examples_smoke.rs b/bench-mobile/tests/examples_smoke.rs new file mode 100644 index 000000000..ecfc17c65 --- /dev/null +++ b/bench-mobile/tests/examples_smoke.rs @@ -0,0 +1,27 @@ +use bench_mobile::examples::{fixture_end_to_end_smoke, prepare_fixture, MobileBenchFixture}; + +#[test] +fn embedded_example_fixtures_prepare_non_empty_artifacts() { + for fixture in [ + MobileBenchFixture::CompleteAgeCheck, + MobileBenchFixture::Oprf, + MobileBenchFixture::P256Bigcurve, + ] { + let prepared = prepare_fixture(fixture).expect("prepare fixture"); + let (constraints, witnesses) = prepared.prover_size(); + + assert!(constraints > 0, "expected non-empty constraint set"); + assert!(witnesses > 0, "expected non-empty witness set"); + } +} + +#[test] +fn embedded_oprf_fixture_proves_and_verifies() { + fixture_end_to_end_smoke(MobileBenchFixture::Oprf).expect("oprf smoke benchmark"); +} + +#[test] +fn embedded_p256_bigcurve_fixture_proves_and_verifies() { + fixture_end_to_end_smoke(MobileBenchFixture::P256Bigcurve) + .expect("p256_bigcurve smoke benchmark"); +} diff --git a/bench-mobile/tests/passport_smoke.rs b/bench-mobile/tests/passport_smoke.rs new file mode 100644 index 000000000..b423b3bbd --- /dev/null +++ b/bench-mobile/tests/passport_smoke.rs @@ -0,0 +1,40 @@ +use bench_mobile::passport::{ + passport_complete_age_check_end_to_end_smoke, passport_fragmented_age_check_end_to_end_smoke, + prepare_complete_age_check_fixture, prepare_fragmented_age_check_fixture, +}; + +#[test] +fn embedded_passport_fixture_prepares_non_empty_artifacts() { + let prepared = prepare_complete_age_check_fixture().expect("prepare fixture"); + let (constraints, witnesses) = prepared.prover_size(); + + assert!(constraints > 0, "expected non-empty constraint set"); + assert!(witnesses > 0, "expected non-empty witness set"); +} + +#[test] +fn embedded_passport_fixture_proves_and_verifies() { + passport_complete_age_check_end_to_end_smoke().expect("passport smoke benchmark"); +} + +#[test] +fn embedded_fragmented_passport_fixture_prepares_non_empty_artifacts() { + let prepared = prepare_fragmented_age_check_fixture().expect("prepare fragmented fixture"); + + for (name, fixture) in [ + ("t_add_dsc_720", prepared.add_dsc), + ("t_add_id_data_720", prepared.add_id_data), + ("t_add_integrity_commit", prepared.add_integrity_commit), + ("t_attest", prepared.attest), + ] { + let (constraints, witnesses) = fixture.prover_size(); + + assert!(constraints > 0, "{name} should have non-empty constraints"); + assert!(witnesses > 0, "{name} should have non-empty witnesses"); + } +} + +#[test] +fn embedded_fragmented_passport_fixture_proves_and_verifies() { + passport_fragmented_age_check_end_to_end_smoke().expect("fragmented passport smoke benchmark"); +} diff --git a/mobench.toml b/mobench.toml new file mode 100644 index 000000000..b654550c4 --- /dev/null +++ b/mobench.toml @@ -0,0 +1,20 @@ +[project] +crate = "bench-mobile" +library_name = "bench_mobile" +ffi_backend = "native-c-abi" + +[android] +package = "dev.world.benchmobile" +min_sdk = 24 +target_sdk = 34 +abis = ["arm64-v8a"] + +[ios] +bundle_id = "dev.world.benchmobile" +deployment_target = "10.0" +runner = "uikit-legacy" + +[browserstack] +ios_completion_timeout_secs = 7200 +android_benchmark_timeout_secs = 7200 +android_heartbeat_interval_secs = 10 diff --git a/noir-examples/noir-passport/merkle_age_check/Nargo.toml b/noir-examples/noir-passport/merkle_age_check/Nargo.toml index 9af68cc58..e4b237fe6 100644 --- a/noir-examples/noir-passport/merkle_age_check/Nargo.toml +++ b/noir-examples/noir-passport/merkle_age_check/Nargo.toml @@ -1,7 +1,9 @@ [workspace] members = [ "t_add_dsc_1850", + "t_add_dsc_720", "t_add_id_data_1850", + "t_add_id_data_720", "t_add_integrity_commit", "t_attest", ] diff --git a/noir-examples/noir-passport/merkle_age_check/benchmark-inputs/tbs_720/t_add_dsc_720.toml b/noir-examples/noir-passport/merkle_age_check/benchmark-inputs/tbs_720/t_add_dsc_720.toml new file mode 100644 index 000000000..995956008 --- /dev/null +++ b/noir-examples/noir-passport/merkle_age_check/benchmark-inputs/tbs_720/t_add_dsc_720.toml @@ -0,0 +1,9 @@ +csc_pubkey = [191, 56, 52, 58, 68, 102, 237, 183, 171, 195, 84, 11, 3, 233, 51, 203, 74, 37, 42, 68, 152, 19, 154, 192, 131, 19, 113, 213, 124, 239, 224, 225, 165, 80, 127, 141, 153, 142, 67, 27, 80, 195, 133, 114, 240, 90, 185, 199, 165, 202, 176, 89, 69, 36, 65, 105, 30, 110, 4, 208, 12, 242, 135, 138, 112, 0, 112, 23, 63, 255, 106, 101, 85, 230, 227, 208, 200, 233, 85, 158, 57, 216, 198, 32, 116, 4, 181, 10, 208, 243, 151, 165, 147, 187, 14, 133, 61, 31, 15, 146, 160, 16, 91, 221, 65, 81, 131, 77, 250, 8, 5, 30, 244, 110, 139, 157, 228, 250, 47, 54, 46, 153, 235, 164, 201, 64, 61, 171, 152, 23, 115, 253, 143, 134, 106, 100, 221, 126, 124, 29, 158, 68, 169, 153, 8, 134, 19, 141, 243, 173, 103, 176, 135, 248, 179, 254, 74, 187, 86, 47, 12, 204, 128, 145, 46, 121, 60, 229, 217, 220, 247, 135, 186, 158, 69, 91, 128, 116, 92, 152, 233, 139, 249, 106, 63, 203, 217, 86, 113, 2, 78, 165, 244, 86, 152, 213, 164, 36, 24, 179, 100, 67, 182, 69, 30, 5, 131, 11, 129, 211, 171, 52, 237, 148, 104, 197, 107, 44, 64, 38, 244, 242, 170, 3, 191, 182, 145, 129, 165, 236, 217, 97, 192, 75, 17, 254, 254, 33, 68, 205, 70, 79, 134, 69, 244, 176, 24, 133, 19, 70, 24, 170, 161, 72, 171, 48, 146, 75, 134, 119, 13, 39, 217, 189, 2, 173, 141, 136, 176, 140, 220, 230, 94, 151, 182, 4, 120, 218, 39, 115, 34, 78, 139, 102, 230, 227, 223, 78, 72, 133, 59, 224, 128, 79, 71, 67, 133, 171, 11, 66, 200, 133, 21, 76, 125, 126, 111, 212, 29, 7, 92, 4, 5, 189, 41, 21, 15, 96, 31, 28, 233, 156, 44, 254, 47, 121, 82, 71, 133, 69, 3, 135, 247, 237, 29, 140, 111, 2, 232, 200, 129, 234, 113, 146, 243, 148, 127, 227, 183, 110, 190, 65, 93, 136, 180, 104, 17, 121, 45, 128, 216, 192, 95, 111, 75, 47, 182, 96, 41, 126, 100, 40, 129, 43, 154, 14, 220, 192, 8, 64, 47, 153, 2, 244, 140, 51, 4, 212, 105, 249, 255, 60, 143, 2, 60, 86, 176, 65, 253, 132, 133, 84, 56, 165, 169, 121, 182, 176, 237, 210, 209, 119, 253, 138, 95, 127, 194, 72, 248, 212, 91, 87, 203, 173, 38, 80, 222, 101, 163, 252, 86, 186, 143, 161, 184, 70, 24, 248, 230, 196, 157, 35, 205, 39, 49, 136, 8, 204, 176, 116, 68, 167, 1, 10, 217, 82, 208, 215, 28, 231, 252, 203, 70, 240, 62, 4, 211, 209, 148, 141, 44, 246, 215, 112, 162, 20, 129, 94, 123, 230, 126, 128, 33, 41, 231, 119, 64, 51, 253, 166, 145, 64, 10, 158, 141, 43, 193, 20, 69, 15, 194, 35, 139, 233, 28, 240, 166, 131, 61, 187, 241, 129] +csc_key_ne_hash = "0x1ca4af41d370d02b729b6a63b4aea3cf29242ad76ac8420c144d7558072ba172" +salt = "0x2" +country = "UTO" +tbs_certificate = [48, 130, 2, 54, 160, 3, 2, 1, 2, 2, 1, 2, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 11, 5, 0, 48, 67, 49, 11, 48, 9, 6, 3, 85, 4, 6, 19, 2, 85, 84, 49, 32, 48, 30, 6, 3, 85, 4, 10, 12, 23, 77, 111, 99, 107, 32, 80, 97, 115, 115, 112, 111, 114, 116, 32, 65, 117, 116, 104, 111, 114, 105, 116, 121, 49, 18, 48, 16, 6, 3, 85, 4, 3, 12, 9, 77, 111, 99, 107, 32, 67, 83, 67, 65, 48, 30, 23, 13, 50, 49, 48, 51, 50, 54, 49, 51, 53, 52, 49, 56, 90, 23, 13, 51, 49, 48, 51, 50, 52, 49, 51, 53, 52, 49, 56, 90, 48, 66, 49, 11, 48, 9, 6, 3, 85, 4, 6, 19, 2, 85, 84, 49, 32, 48, 30, 6, 3, 85, 4, 10, 12, 23, 77, 111, 99, 107, 32, 80, 97, 115, 115, 112, 111, 114, 116, 32, 65, 117, 116, 104, 111, 114, 105, 116, 121, 49, 17, 48, 15, 6, 3, 85, 4, 3, 12, 8, 77, 111, 99, 107, 32, 68, 83, 67, 48, 130, 1, 34, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 1, 15, 0, 48, 130, 1, 10, 2, 130, 1, 1, 0, 144, 96, 22, 98, 202, 23, 238, 6, 187, 83, 246, 10, 141, 149, 39, 62, 150, 207, 25, 76, 254, 121, 159, 193, 25, 17, 64, 229, 112, 170, 152, 94, 212, 213, 4, 191, 8, 183, 225, 184, 213, 181, 211, 100, 210, 60, 155, 26, 13, 219, 11, 116, 84, 236, 33, 212, 47, 5, 187, 226, 120, 161, 57, 97, 200, 250, 174, 139, 216, 171, 95, 178, 148, 109, 3, 137, 151, 245, 142, 53, 177, 251, 74, 202, 2, 157, 33, 55, 30, 189, 239, 243, 101, 183, 43, 68, 245, 198, 9, 90, 109, 89, 109, 33, 98, 32, 173, 121, 203, 2, 79, 68, 150, 135, 158, 72, 76, 223, 55, 66, 30, 45, 33, 16, 91, 153, 158, 127, 64, 221, 31, 151, 241, 93, 105, 235, 153, 176, 146, 221, 20, 231, 141, 2, 146, 77, 209, 30, 90, 33, 33, 232, 176, 145, 244, 229, 221, 43, 101, 10, 210, 55, 50, 200, 103, 87, 18, 82, 53, 193, 130, 124, 69, 96, 179, 87, 245, 203, 181, 205, 57, 67, 181, 80, 198, 57, 101, 151, 179, 103, 201, 243, 52, 68, 91, 122, 137, 209, 141, 39, 68, 73, 244, 200, 211, 125, 2, 176, 12, 80, 77, 81, 225, 169, 34, 209, 187, 212, 47, 56, 92, 220, 159, 89, 236, 133, 200, 211, 11, 237, 217, 129, 115, 191, 208, 39, 198, 179, 16, 28, 59, 121, 160, 48, 239, 81, 144, 102, 168, 122, 158, 59, 83, 54, 91, 211, 2, 3, 1, 0, 1, 163, 78, 48, 76, 48, 15, 6, 3, 85, 29, 19, 1, 1, 255, 4, 5, 48, 3, 1, 1, 0, 48, 14, 6, 3, 85, 29, 15, 1, 1, 255, 4, 4, 3, 2, 7, 128, 48, 41, 6, 3, 85, 29, 14, 4, 34, 4, 32, 236, 115, 196, 36, 236, 2, 138, 16, 34, 153, 224, 23, 230, 87, 56, 253, 158, 235, 14, 147, 38, 52, 87, 5, 72, 19, 213, 247, 131, 38, 127, 141, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] +csc_pubkey_redc_param = [21, 107, 159, 157, 72, 119, 18, 0, 27, 71, 177, 110, 89, 195, 140, 32, 0, 81, 204, 142, 10, 42, 57, 174, 56, 49, 20, 174, 40, 168, 13, 110, 119, 62, 130, 206, 113, 131, 163, 69, 216, 148, 52, 169, 100, 129, 114, 255, 46, 231, 61, 14, 80, 203, 136, 94, 50, 194, 33, 127, 20, 160, 234, 71, 20, 201, 8, 231, 223, 0, 192, 38, 138, 232, 188, 101, 68, 103, 102, 81, 27, 78, 37, 96, 11, 135, 61, 12, 158, 37, 141, 215, 151, 25, 176, 135, 41, 133, 163, 113, 221, 161, 175, 226, 9, 113, 252, 229, 239, 48, 55, 162, 33, 178, 224, 94, 18, 161, 220, 186, 163, 10, 133, 85, 127, 74, 95, 74, 192, 164, 69, 236, 121, 95, 224, 115, 181, 169, 156, 121, 161, 180, 127, 61, 26, 113, 65, 35, 241, 87, 67, 152, 40, 160, 29, 190, 249, 119, 178, 40, 99, 198, 222, 102, 162, 68, 138, 169, 237, 193, 199, 151, 159, 80, 118, 20, 141, 97, 224, 76, 212, 29, 80, 238, 32, 234, 172, 151, 141, 134, 227, 177, 61, 106, 9, 105, 194, 149, 232, 171, 165, 135, 244, 24, 214, 213, 28, 115, 68, 75, 160, 198, 129, 73, 238, 59, 59, 4, 45, 101, 235, 220, 224, 224, 5, 76, 13, 218, 137, 189, 174, 52, 38, 192, 245, 127, 138, 81, 96, 255, 162, 119, 44, 210, 247, 66, 99, 3, 202, 110, 26, 174, 27, 157, 15, 85, 81, 115, 162, 35, 217, 73, 84, 139, 198, 206, 205, 93, 221, 207, 182, 126, 20, 211, 178, 23, 232, 95, 253, 252, 254, 211, 143, 149, 130, 102, 69, 47, 230, 141, 23, 107, 148, 35, 98, 85, 98, 111, 238, 85, 148, 111, 251, 83, 220, 88, 156, 81, 27, 196, 8, 5, 66, 216, 111, 3, 226, 212, 80, 151, 38, 164, 172, 189, 112, 224, 225, 98, 165, 86, 180, 31, 32, 249, 202, 127, 244, 142, 127, 17, 239, 16, 41, 1, 191, 113, 134, 18, 66, 251, 227, 254, 73, 53, 180, 104, 27, 133, 32, 198, 218, 159, 226, 32, 79, 136, 115, 52, 110, 242, 239, 204, 109, 154, 29, 180, 85, 142, 244, 160, 90, 14, 37, 236, 159, 130, 229, 169, 11, 37, 132, 37, 49, 124, 225, 206, 164, 202, 94, 34, 8, 5, 49, 56, 17, 171, 65, 211, 126, 42, 109, 62, 176, 132, 107, 62, 190, 141, 214, 11, 217, 6, 52, 198, 157, 181, 22, 107, 245, 249, 222, 4, 71, 63, 54, 104, 23, 171, 180, 131, 16, 230, 23, 94, 39, 61, 149, 204, 15, 42, 7, 187, 147, 37, 55, 67, 188, 147, 194, 254, 154, 193, 95, 227, 162, 216, 3, 127, 116, 248, 115, 121, 126, 176, 253, 175, 7, 245, 175, 129, 254, 70, 151, 36, 174, 235, 172, 158, 244, 206, 119, 184, 231, 1, 14, 162, 152, 159, 97, 136, 82, 216, 75, 161, 36, 208, 59, 62, 13, 12, 35, 82, 236] +dsc_signature = [169, 42, 245, 251, 152, 33, 198, 125, 189, 117, 149, 90, 241, 101, 47, 107, 181, 214, 135, 197, 190, 87, 41, 132, 13, 13, 229, 24, 50, 69, 107, 110, 96, 187, 25, 255, 239, 191, 119, 183, 222, 27, 253, 249, 206, 136, 163, 219, 239, 172, 234, 113, 51, 126, 12, 10, 149, 204, 190, 228, 114, 168, 122, 60, 145, 26, 71, 80, 146, 216, 159, 114, 116, 88, 216, 83, 9, 71, 153, 115, 143, 11, 248, 141, 66, 132, 223, 42, 96, 25, 14, 77, 180, 82, 168, 237, 218, 211, 4, 242, 47, 229, 15, 248, 188, 41, 165, 58, 135, 172, 69, 220, 187, 34, 57, 183, 1, 238, 137, 17, 123, 17, 8, 96, 188, 99, 94, 176, 175, 217, 113, 187, 61, 192, 225, 146, 10, 65, 87, 54, 165, 129, 189, 17, 29, 46, 200, 232, 214, 79, 156, 181, 37, 24, 135, 191, 3, 115, 81, 225, 50, 41, 12, 202, 44, 86, 22, 118, 228, 72, 251, 24, 78, 202, 218, 71, 78, 101, 120, 238, 206, 86, 14, 129, 178, 211, 38, 84, 127, 106, 19, 118, 128, 66, 126, 193, 72, 162, 130, 152, 151, 3, 123, 122, 77, 149, 236, 170, 240, 99, 106, 59, 252, 176, 56, 139, 119, 12, 212, 130, 112, 124, 95, 34, 195, 84, 212, 60, 44, 30, 67, 225, 252, 121, 148, 105, 151, 38, 174, 187, 138, 60, 246, 67, 223, 52, 245, 245, 3, 168, 90, 53, 170, 49, 33, 117, 177, 200, 156, 203, 149, 17, 84, 139, 50, 110, 39, 83, 156, 245, 128, 145, 71, 184, 125, 100, 88, 221, 21, 10, 235, 93, 227, 156, 131, 10, 69, 202, 217, 188, 213, 80, 128, 14, 76, 17, 178, 39, 105, 155, 139, 180, 80, 84, 137, 103, 217, 87, 221, 155, 111, 245, 133, 253, 54, 84, 62, 79, 184, 210, 233, 206, 77, 202, 36, 137, 126, 231, 171, 182, 52, 226, 127, 205, 232, 187, 36, 52, 39, 194, 42, 93, 224, 205, 123, 162, 5, 181, 168, 113, 37, 22, 122, 48, 48, 121, 5, 14, 220, 181, 246, 68, 240, 222, 40, 46, 161, 244, 167, 121, 105, 60, 95, 233, 25, 168, 174, 31, 124, 89, 40, 95, 19, 188, 221, 212, 225, 228, 51, 137, 2, 170, 190, 66, 236, 133, 80, 130, 59, 159, 151, 234, 49, 43, 155, 253, 137, 101, 175, 254, 10, 146, 203, 190, 234, 30, 247, 81, 97, 108, 22, 19, 176, 5, 7, 55, 146, 221, 177, 161, 128, 96, 70, 168, 1, 164, 146, 17, 75, 84, 123, 45, 246, 75, 147, 119, 251, 70, 128, 30, 237, 242, 17, 111, 139, 187, 32, 228, 36, 196, 226, 133, 120, 254, 62, 145, 120, 13, 97, 171, 152, 77, 100, 122, 47, 101, 100, 206, 101, 30, 75, 118, 115, 39, 79, 108, 222, 47, 167, 161, 234, 160, 180, 114, 6, 161, 205, 107, 242, 191, 178, 177, 196, 6, 106, 228, 62, 34, 177, 85, 129, 223] +exponent = 65537 +tbs_certificate_len = 570 diff --git a/noir-examples/noir-passport/merkle_age_check/benchmark-inputs/tbs_720/t_add_id_data_720.toml b/noir-examples/noir-passport/merkle_age_check/benchmark-inputs/tbs_720/t_add_id_data_720.toml new file mode 100644 index 000000000..286711b2c --- /dev/null +++ b/noir-examples/noir-passport/merkle_age_check/benchmark-inputs/tbs_720/t_add_id_data_720.toml @@ -0,0 +1,13 @@ +comm_in = "0x21a7b23d758c007e32bd6eb30d925f52487a5721baeae6717757ef34c7584b97" +salt_in = "0x2" +salt_out = "0x3" +dg1 = [97, 91, 95, 31, 88, 80, 60, 85, 84, 79, 68, 79, 69, 60, 60, 74, 79, 72, 78, 60, 77, 79, 67, 75, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 76, 56, 57, 56, 57, 48, 50, 67, 51, 54, 85, 84, 79, 48, 55, 48, 49, 48, 49, 57, 77, 51, 50, 48, 49, 48, 49, 53, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 48, 56, 0, 0] +dsc_pubkey = [144, 96, 22, 98, 202, 23, 238, 6, 187, 83, 246, 10, 141, 149, 39, 62, 150, 207, 25, 76, 254, 121, 159, 193, 25, 17, 64, 229, 112, 170, 152, 94, 212, 213, 4, 191, 8, 183, 225, 184, 213, 181, 211, 100, 210, 60, 155, 26, 13, 219, 11, 116, 84, 236, 33, 212, 47, 5, 187, 226, 120, 161, 57, 97, 200, 250, 174, 139, 216, 171, 95, 178, 148, 109, 3, 137, 151, 245, 142, 53, 177, 251, 74, 202, 2, 157, 33, 55, 30, 189, 239, 243, 101, 183, 43, 68, 245, 198, 9, 90, 109, 89, 109, 33, 98, 32, 173, 121, 203, 2, 79, 68, 150, 135, 158, 72, 76, 223, 55, 66, 30, 45, 33, 16, 91, 153, 158, 127, 64, 221, 31, 151, 241, 93, 105, 235, 153, 176, 146, 221, 20, 231, 141, 2, 146, 77, 209, 30, 90, 33, 33, 232, 176, 145, 244, 229, 221, 43, 101, 10, 210, 55, 50, 200, 103, 87, 18, 82, 53, 193, 130, 124, 69, 96, 179, 87, 245, 203, 181, 205, 57, 67, 181, 80, 198, 57, 101, 151, 179, 103, 201, 243, 52, 68, 91, 122, 137, 209, 141, 39, 68, 73, 244, 200, 211, 125, 2, 176, 12, 80, 77, 81, 225, 169, 34, 209, 187, 212, 47, 56, 92, 220, 159, 89, 236, 133, 200, 211, 11, 237, 217, 129, 115, 191, 208, 39, 198, 179, 16, 28, 59, 121, 160, 48, 239, 81, 144, 102, 168, 122, 158, 59, 83, 54, 91, 211] +dsc_pubkey_redc_param = [28, 94, 216, 205, 130, 214, 187, 182, 58, 208, 228, 159, 128, 141, 147, 245, 68, 203, 236, 129, 99, 140, 108, 211, 245, 198, 71, 176, 2, 196, 241, 58, 221, 37, 54, 244, 93, 131, 148, 193, 87, 121, 38, 188, 142, 196, 4, 105, 26, 37, 150, 148, 152, 205, 235, 126, 184, 93, 105, 56, 44, 19, 57, 156, 74, 145, 52, 201, 54, 91, 218, 1, 26, 107, 219, 199, 28, 10, 57, 32, 22, 195, 131, 58, 46, 165, 57, 181, 53, 133, 182, 229, 180, 5, 229, 103, 172, 187, 96, 43, 14, 4, 151, 199, 136, 53, 224, 199, 167, 81, 240, 180, 174, 254, 87, 255, 239, 218, 1, 170, 8, 126, 189, 0, 83, 125, 173, 191, 84, 53, 29, 80, 88, 48, 59, 50, 243, 156, 221, 1, 81, 7, 140, 195, 28, 126, 195, 88, 226, 224, 141, 129, 220, 242, 189, 217, 16, 44, 163, 154, 247, 61, 237, 213, 56, 204, 14, 199, 251, 110, 139, 117, 142, 16, 234, 116, 47, 82, 226, 88, 40, 15, 104, 74, 12, 48, 224, 229, 64, 4, 157, 1, 124, 203, 51, 181, 191, 194, 149, 113, 225, 34, 173, 236, 206, 22, 80, 189, 181, 158, 100, 248, 60, 60, 68, 157, 169, 68, 26, 229, 226, 151, 181, 39, 197, 51, 51, 171, 197, 130, 196, 219, 115, 145, 84, 69, 157, 247, 71, 141, 198, 109, 219, 255, 149, 228, 19, 23, 56, 175, 123, 107, 192, 219, 175, 130, 60] +dsc_pubkey_offset_in_dsc_cert = 229 +exponent_offset_in_dsc_cert = 487 +sod_signature = [55, 125, 38, 168, 223, 78, 57, 118, 228, 58, 135, 97, 165, 230, 168, 0, 91, 69, 158, 155, 110, 40, 239, 57, 178, 211, 54, 96, 253, 10, 240, 107, 184, 122, 27, 253, 124, 73, 89, 217, 97, 144, 99, 173, 170, 199, 64, 96, 11, 143, 50, 90, 234, 133, 108, 224, 104, 98, 171, 85, 251, 138, 248, 6, 241, 193, 212, 242, 58, 208, 107, 18, 239, 246, 157, 218, 85, 217, 68, 118, 211, 205, 230, 40, 68, 3, 2, 171, 231, 247, 10, 101, 21, 250, 17, 217, 56, 254, 79, 109, 61, 190, 241, 168, 231, 49, 118, 0, 114, 237, 216, 227, 110, 117, 137, 176, 223, 146, 29, 1, 172, 234, 23, 186, 203, 18, 188, 210, 42, 156, 133, 107, 124, 3, 244, 173, 53, 85, 75, 225, 4, 106, 118, 111, 35, 16, 133, 227, 31, 148, 60, 113, 230, 201, 147, 83, 236, 179, 245, 84, 84, 144, 62, 29, 198, 38, 19, 237, 145, 183, 6, 72, 247, 172, 73, 108, 87, 50, 128, 85, 152, 180, 222, 24, 88, 18, 149, 23, 217, 196, 219, 198, 93, 53, 3, 189, 190, 34, 221, 60, 73, 108, 54, 42, 162, 49, 195, 65, 119, 194, 214, 101, 35, 101, 247, 27, 29, 143, 169, 47, 249, 107, 101, 202, 185, 231, 48, 223, 63, 20, 164, 24, 38, 103, 147, 80, 168, 152, 159, 186, 58, 107, 188, 155, 29, 221, 45, 107, 173, 106, 69, 98, 155, 39, 194, 22] +tbs_certificate = [48, 130, 2, 54, 160, 3, 2, 1, 2, 2, 1, 2, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 11, 5, 0, 48, 67, 49, 11, 48, 9, 6, 3, 85, 4, 6, 19, 2, 85, 84, 49, 32, 48, 30, 6, 3, 85, 4, 10, 12, 23, 77, 111, 99, 107, 32, 80, 97, 115, 115, 112, 111, 114, 116, 32, 65, 117, 116, 104, 111, 114, 105, 116, 121, 49, 18, 48, 16, 6, 3, 85, 4, 3, 12, 9, 77, 111, 99, 107, 32, 67, 83, 67, 65, 48, 30, 23, 13, 50, 49, 48, 51, 50, 54, 49, 51, 53, 52, 49, 56, 90, 23, 13, 51, 49, 48, 51, 50, 52, 49, 51, 53, 52, 49, 56, 90, 48, 66, 49, 11, 48, 9, 6, 3, 85, 4, 6, 19, 2, 85, 84, 49, 32, 48, 30, 6, 3, 85, 4, 10, 12, 23, 77, 111, 99, 107, 32, 80, 97, 115, 115, 112, 111, 114, 116, 32, 65, 117, 116, 104, 111, 114, 105, 116, 121, 49, 17, 48, 15, 6, 3, 85, 4, 3, 12, 8, 77, 111, 99, 107, 32, 68, 83, 67, 48, 130, 1, 34, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 1, 5, 0, 3, 130, 1, 15, 0, 48, 130, 1, 10, 2, 130, 1, 1, 0, 144, 96, 22, 98, 202, 23, 238, 6, 187, 83, 246, 10, 141, 149, 39, 62, 150, 207, 25, 76, 254, 121, 159, 193, 25, 17, 64, 229, 112, 170, 152, 94, 212, 213, 4, 191, 8, 183, 225, 184, 213, 181, 211, 100, 210, 60, 155, 26, 13, 219, 11, 116, 84, 236, 33, 212, 47, 5, 187, 226, 120, 161, 57, 97, 200, 250, 174, 139, 216, 171, 95, 178, 148, 109, 3, 137, 151, 245, 142, 53, 177, 251, 74, 202, 2, 157, 33, 55, 30, 189, 239, 243, 101, 183, 43, 68, 245, 198, 9, 90, 109, 89, 109, 33, 98, 32, 173, 121, 203, 2, 79, 68, 150, 135, 158, 72, 76, 223, 55, 66, 30, 45, 33, 16, 91, 153, 158, 127, 64, 221, 31, 151, 241, 93, 105, 235, 153, 176, 146, 221, 20, 231, 141, 2, 146, 77, 209, 30, 90, 33, 33, 232, 176, 145, 244, 229, 221, 43, 101, 10, 210, 55, 50, 200, 103, 87, 18, 82, 53, 193, 130, 124, 69, 96, 179, 87, 245, 203, 181, 205, 57, 67, 181, 80, 198, 57, 101, 151, 179, 103, 201, 243, 52, 68, 91, 122, 137, 209, 141, 39, 68, 73, 244, 200, 211, 125, 2, 176, 12, 80, 77, 81, 225, 169, 34, 209, 187, 212, 47, 56, 92, 220, 159, 89, 236, 133, 200, 211, 11, 237, 217, 129, 115, 191, 208, 39, 198, 179, 16, 28, 59, 121, 160, 48, 239, 81, 144, 102, 168, 122, 158, 59, 83, 54, 91, 211, 2, 3, 1, 0, 1, 163, 78, 48, 76, 48, 15, 6, 3, 85, 29, 19, 1, 1, 255, 4, 5, 48, 3, 1, 1, 0, 48, 14, 6, 3, 85, 29, 15, 1, 1, 255, 4, 4, 3, 2, 7, 128, 48, 41, 6, 3, 85, 29, 14, 4, 34, 4, 32, 236, 115, 196, 36, 236, 2, 138, 16, 34, 153, 224, 23, 230, 87, 56, 253, 158, 235, 14, 147, 38, 52, 87, 5, 72, 19, 213, 247, 131, 38, 127, 141, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] +signed_attributes = [49, 72, 48, 21, 6, 9, 42, 134, 72, 134, 247, 13, 1, 9, 3, 49, 8, 6, 6, 103, 129, 8, 1, 1, 1, 48, 47, 6, 9, 42, 134, 72, 134, 247, 13, 1, 9, 4, 49, 34, 4, 32, 83, 213, 2, 32, 92, 70, 157, 44, 204, 36, 20, 171, 99, 224, 204, 169, 133, 155, 24, 174, 188, 45, 208, 99, 91, 43, 205, 208, 68, 46, 41, 217, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] +exponent = 65537 +e_content = [48, 96, 2, 1, 0, 48, 11, 6, 9, 96, 134, 72, 1, 101, 3, 4, 2, 1, 48, 78, 48, 37, 2, 1, 1, 4, 32, 133, 239, 49, 15, 44, 106, 246, 249, 184, 107, 11, 51, 210, 155, 75, 109, 226, 131, 181, 170, 13, 64, 12, 245, 141, 228, 185, 127, 108, 212, 235, 9, 48, 37, 2, 1, 2, 4, 32, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] diff --git a/noir-examples/noir-passport/merkle_age_check/benchmark-inputs/tbs_720/t_add_integrity_commit.toml b/noir-examples/noir-passport/merkle_age_check/benchmark-inputs/tbs_720/t_add_integrity_commit.toml new file mode 100644 index 000000000..bfb38afed --- /dev/null +++ b/noir-examples/noir-passport/merkle_age_check/benchmark-inputs/tbs_720/t_add_integrity_commit.toml @@ -0,0 +1,11 @@ +comm_in = "0x179c66496e0cfa29827752e404165e7f5e0dd87270d7efe1de0657a174119642" +salt_in = "0x3" +dg1 = [97, 91, 95, 31, 88, 80, 60, 85, 84, 79, 68, 79, 69, 60, 60, 74, 79, 72, 78, 60, 77, 79, 67, 75, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 76, 56, 57, 56, 57, 48, 50, 67, 51, 54, 85, 84, 79, 48, 55, 48, 49, 48, 49, 57, 77, 51, 50, 48, 49, 48, 49, 53, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 48, 56, 0, 0] +dg1_padded_length = 95 +dg1_hash_offset = 27 +signed_attributes = [49, 72, 48, 21, 6, 9, 42, 134, 72, 134, 247, 13, 1, 9, 3, 49, 8, 6, 6, 103, 129, 8, 1, 1, 1, 48, 47, 6, 9, 42, 134, 72, 134, 247, 13, 1, 9, 4, 49, 34, 4, 32, 83, 213, 2, 32, 92, 70, 157, 44, 204, 36, 20, 171, 99, 224, 204, 169, 133, 155, 24, 174, 188, 45, 208, 99, 91, 43, 205, 208, 68, 46, 41, 217, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] +signed_attributes_size = 74 +e_content = [48, 96, 2, 1, 0, 48, 11, 6, 9, 96, 134, 72, 1, 101, 3, 4, 2, 1, 48, 78, 48, 37, 2, 1, 1, 4, 32, 133, 239, 49, 15, 44, 106, 246, 249, 184, 107, 11, 51, 210, 155, 75, 109, 226, 131, 181, 170, 13, 64, 12, 245, 141, 228, 185, 127, 108, 212, 235, 9, 48, 37, 2, 1, 2, 4, 32, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] +e_content_len = 98 +private_nullifier = "0x2cd723116eacc7d3de35eea7fae04ee08682eccb1cc0becd843b4af6c134b4c5" +r_dg1 = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" diff --git a/noir-examples/noir-passport/merkle_age_check/benchmark-inputs/tbs_720/t_attest.toml b/noir-examples/noir-passport/merkle_age_check/benchmark-inputs/tbs_720/t_attest.toml new file mode 100644 index 000000000..b8f2f863e --- /dev/null +++ b/noir-examples/noir-passport/merkle_age_check/benchmark-inputs/tbs_720/t_attest.toml @@ -0,0 +1,37 @@ +root = "0x2ff94ac92b298144a71565dfb425cc9562494d5cee33a9d75a782cf9f3197d59" +current_date = "1735689600" +service_scope = "0x0000000000000000000000000000000000000000000000000000000000000000" +service_subscope = "0x0000000000000000000000000000000000000000000000000000000000000000" +dg1 = [97, 91, 95, 31, 88, 80, 60, 85, 84, 79, 68, 79, 69, 60, 60, 74, 79, 72, 78, 60, 77, 79, 67, 75, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 76, 56, 57, 56, 57, 48, 50, 67, 51, 54, 85, 84, 79, 48, 55, 48, 49, 48, 49, 57, 77, 51, 50, 48, 49, 48, 49, 53, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 48, 56, 0, 0] +r_dg1 = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" +sod_hash = "0x237a444692c7c843c43a8c5af58fd9e1ee9f6b4032db7431fb34ff8e9af060fc" +leaf_index = "0" +merkle_path = [ + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000000" +] +min_age_required = "18" +max_age_required = "0" +nullifier_secret = "0x0000000000000000000000000000000000000000000000000000000000000000" diff --git a/noir-examples/noir-passport/merkle_age_check/t_add_dsc_720/Nargo.toml b/noir-examples/noir-passport/merkle_age_check/t_add_dsc_720/Nargo.toml new file mode 100644 index 000000000..4069a1a85 --- /dev/null +++ b/noir-examples/noir-passport/merkle_age_check/t_add_dsc_720/Nargo.toml @@ -0,0 +1,10 @@ +[package] +name = "t_add_dsc_720" +type = "bin" +compiler_version = ">=1.0.0" + +[dependencies] +sig_check_rsa = { path = "../../utils/sig-check/rsa" } +utils = { path = "../../utils/utils" } +commitment = { path = "../../utils/commitment/common" } +poseidon = { tag = "v0.1.1", git = "https://github.com/noir-lang/poseidon" } diff --git a/noir-examples/noir-passport/merkle_age_check/t_add_dsc_720/src/main.nr b/noir-examples/noir-passport/merkle_age_check/t_add_dsc_720/src/main.nr new file mode 100644 index 000000000..d65d4a7e6 --- /dev/null +++ b/noir-examples/noir-passport/merkle_age_check/t_add_dsc_720/src/main.nr @@ -0,0 +1,44 @@ +// Verify CSCA signed DSC certificate (720-byte TBS) +use commitment::hash_salt_country_tbs; +use sig_check_rsa::{compute_key_ne_hash, verify_signature}; +use utils::get_asn1_element_length; +use utils::types::Alpha3CountryCode; + +fn main( + csc_pubkey: pub [u8; 512], + csc_key_ne_hash: pub Field, + csc_pubkey_redc_param: [u8; 513], + salt: Field, + country: Alpha3CountryCode, + tbs_certificate: [u8; 720], + dsc_signature: [u8; 512], + exponent: u32, + tbs_certificate_len: u32, +) -> pub Field { + // Validate tbs_certificate_len bounds + assert(tbs_certificate_len <= 720, "tbs_certificate_len must not exceed 720"); + + // Validate prover-supplied tbs_certificate_len against ASN.1 DER header + assert(tbs_certificate[0] == 0x30, "TBS must start with SEQUENCE tag"); + let computed_len = get_asn1_element_length(tbs_certificate); + assert(tbs_certificate_len == computed_len, "tbs_certificate_len does not match ASN.1 header"); + assert( + csc_key_ne_hash == compute_key_ne_hash::<512>(csc_pubkey, exponent), + "CSC key hash does not match (n||e)", + ); + + assert( + verify_signature::<512, 0, 720, 32>( + csc_pubkey, + dsc_signature, + csc_pubkey_redc_param, + exponent, + tbs_certificate, + tbs_certificate_len, + 0, + ), + "RSA signature verification failed", + ); + let comm_out = hash_salt_country_tbs(salt, country, tbs_certificate); + comm_out +} diff --git a/noir-examples/noir-passport/merkle_age_check/t_add_id_data_720/Nargo.toml b/noir-examples/noir-passport/merkle_age_check/t_add_id_data_720/Nargo.toml new file mode 100644 index 000000000..b223b94b2 --- /dev/null +++ b/noir-examples/noir-passport/merkle_age_check/t_add_id_data_720/Nargo.toml @@ -0,0 +1,10 @@ +[package] +name = "t_add_id_data_720" +type = "bin" +compiler_version = ">=1.0.0" + +[dependencies] +sig_check_rsa = { path = "../../utils/sig-check/rsa" } +utils = { path = "../../utils/utils" } +data_check_tbs_pubkey = { path = "../../utils/data-check/tbs-pubkey" } +commitment = { path = "../../utils/commitment/dsc-to-id" } diff --git a/noir-examples/noir-passport/merkle_age_check/t_add_id_data_720/src/main.nr b/noir-examples/noir-passport/merkle_age_check/t_add_id_data_720/src/main.nr new file mode 100644 index 000000000..a0b45d6ce --- /dev/null +++ b/noir-examples/noir-passport/merkle_age_check/t_add_id_data_720/src/main.nr @@ -0,0 +1,63 @@ +// Verify DSC signed passport SignedAttributes (720-byte TBS) +use commitment::commit_to_id; +use data_check_tbs_pubkey::verify_rsa_pubkey_in_tbs; +use sig_check_rsa::verify_signature; +use utils::get_asn1_element_length; +use utils::types::{DG1Data, EContentData, SignedAttrsData}; + +fn main( + comm_in: pub Field, + salt_in: Field, + salt_out: Field, + dg1: DG1Data, + dsc_pubkey: [u8; 256], + dsc_pubkey_redc_param: [u8; 257], + dsc_pubkey_offset_in_dsc_cert: u32, + exponent: u32, + exponent_offset_in_dsc_cert: u32, + sod_signature: [u8; 256], + tbs_certificate: [u8; 720], + signed_attributes: SignedAttrsData, + e_content: EContentData, +) -> pub Field { + // Compute TBS certificate length from DER header + assert(tbs_certificate[0] == 0x30, "TBS must start with SEQUENCE tag"); + let tbs_certificate_len = get_asn1_element_length(tbs_certificate); + + verify_rsa_pubkey_in_tbs( + dsc_pubkey, + tbs_certificate, + dsc_pubkey_offset_in_dsc_cert, + tbs_certificate_len, + exponent, + exponent_offset_in_dsc_cert, + ); + + // Compute signed_attributes_size from ASN.1 header + let signed_attributes_size = get_asn1_element_length(signed_attributes); + + assert( + verify_signature::<256, 0, 200, 32>( + dsc_pubkey, + sod_signature, + dsc_pubkey_redc_param, + exponent, + signed_attributes, + signed_attributes_size, + 0, + ), + "RSA signature verification failed", + ); + let comm_out = commit_to_id( + comm_in, + salt_in, + salt_out, + dg1, + tbs_certificate, + sod_signature, + signed_attributes, + signed_attributes_size as Field, + e_content, + ); + comm_out +} diff --git a/noir-examples/oprf/src/main.nr b/noir-examples/oprf/src/main.nr index c87f7c8b3..47e74dbeb 100644 --- a/noir-examples/oprf/src/main.nr +++ b/noir-examples/oprf/src/main.nr @@ -8,7 +8,7 @@ pub(crate) mod merkle_proof; use constants::{MAX_DEPTH, NUM_KEYS}; use nullifier::oprf_nullifier; -use types::{OprfNullifierInputs, OprfNullifierOutputs, PublicKey}; +use types::{OprfNullifierInputs, PublicKey}; pub fn main( cred_pk: pub PublicKey, @@ -18,11 +18,11 @@ pub fn main( rp_id: pub Field, action: pub Field, oprf_pk: pub PublicKey, - nonce: pub Field, - signal_hash: pub Field, + nonce: Field, + signal_hash: Field, inputs: OprfNullifierInputs, -) -> pub OprfNullifierOutputs { - oprf_nullifier( +) { + let _outputs = oprf_nullifier( cred_pk, current_time_stamp, root, @@ -33,5 +33,5 @@ pub fn main( nonce, signal_hash, inputs, - ) + ); } diff --git a/noir-examples/p256_bigcurve/Nargo.toml b/noir-examples/p256_bigcurve/Nargo.toml index b9a721290..78429e631 100644 --- a/noir-examples/p256_bigcurve/Nargo.toml +++ b/noir-examples/p256_bigcurve/Nargo.toml @@ -4,5 +4,5 @@ type = "bin" authors = [""] [dependencies] -bignum = { tag = "v0.6.0", git = "https://github.com/noir-lang/noir-bignum" } -bigcurve = { tag = "v0.7.0", git = "https://github.com/noir-lang/noir_bigcurve" } +bignum = { tag = "v0.8.0", git = "https://github.com/noir-lang/noir-bignum" } +bigcurve = { tag = "v0.11.0", git = "https://github.com/noir-lang/noir_bigcurve" } diff --git a/noir-examples/p256_bigcurve/src/main.nr b/noir-examples/p256_bigcurve/src/main.nr index c157fe1ff..4628196bc 100644 --- a/noir-examples/p256_bigcurve/src/main.nr +++ b/noir-examples/p256_bigcurve/src/main.nr @@ -1,35 +1,39 @@ -use bignum::{BigNum, BigNumTrait}; -use bigcurve::{BigCurveTrait, curves::secp256r1::{Secp256r1, Secp256r1Fq, Secp256r1Fr, Secp256r1Scalar}, scalar_field::ScalarFieldTrait}; +use bigcurve::{ + BigCurve, + curves::secp256r1::{Secp256r1, Secp256r1_Fq, Secp256r1_Fr, Secp256r1Scalar}, +}; +use bignum::BigNum; -fn main( - hashed_message : [u8;32], - pub_key_x : [u8;32], - pub_key_y : [u8;32], - signature : [u8;64] -) { +fn main(hashed_message: [u8; 32], pub_key_x: [u8; 32], pub_key_y: [u8; 32], signature: [u8; 64]) { let gen = Secp256r1::one(); let public = Secp256r1 { - x: BigNum::from_be_bytes(pub_key_x), - y: BigNum::from_be_bytes(pub_key_y), + x: Secp256r1_Fq::from_be_bytes(pub_key_x), + y: Secp256r1_Fq::from_be_bytes(pub_key_y), is_infinity: false, }; public.validate_on_curve(); - let message = Secp256r1Fr::from_be_bytes(hashed_message); - + let message = Secp256r1_Fr::from_be_bytes(hashed_message); + // Somehow Noir makes it harder than necessary to get the signature components. - let mut r: [u8; 32] = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; - let mut s: [u8; 32] = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; + let mut r: [u8; 32] = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, + ]; + let mut s: [u8; 32] = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, + ]; for i in 0..32 { r[i] = signature[i]; s[i] = signature[i + 32]; } - let r_x = Secp256r1Fq::from_be_bytes(r); - let r = Secp256r1Fr::from_be_bytes(r); - let s = Secp256r1Fr::from_be_bytes(s); + let r_x = Secp256r1_Fq::from_be_bytes(r); + let r = Secp256r1_Fr::from_be_bytes(r); + let s = Secp256r1_Fr::from_be_bytes(s); let s_g = Secp256r1Scalar::from_bignum(message / s); let s_p = Secp256r1Scalar::from_bignum(r / s); - let r_point = Secp256r1::msm([gen, public], [s_g, s_p]); + let r_point = Secp256r1::evaluate_linear_expression([gen, public], [s_g, s_p], []); assert(r_point.x == r_x); } diff --git a/provekit/prover/src/lib.rs b/provekit/prover/src/lib.rs index a9c6a83f8..98f91c0ca 100644 --- a/provekit/prover/src/lib.rs +++ b/provekit/prover/src/lib.rs @@ -1,7 +1,10 @@ #[cfg(test)] use crate::r1cs::R1CSSolver; use { - crate::whir_r1cs::WhirR1CSProver, + crate::{ + r1cs::{CompressedLayers, CompressedR1CS}, + whir_r1cs::WhirR1CSProver, + }, acir::native_types::{Witness, WitnessMap}, anyhow::{Context, Result}, provekit_common::{ @@ -119,9 +122,12 @@ impl Prove for Prover { drop(self.program); drop(self.witness_generator); - let r1cs = self.r1cs; - let num_witnesses = r1cs.num_witnesses(); - let num_constraints = r1cs.num_constraints(); + // R1CS matrices are only needed at sumcheck; compress to free memory during + // commits. + let compressed_r1cs = + CompressedR1CS::compress(self.r1cs).context("While compressing R1CS")?; + let num_witnesses = compressed_r1cs.num_witnesses(); + let num_constraints = compressed_r1cs.num_constraints(); // Set up transcript with public inputs bound to the instance. let instance = public_inputs.hash_bytes(); @@ -147,10 +153,14 @@ impl Prove for Prover { .context("While solving w1 witnesses")?; } - // Hold w2 layers across the w1 commit only when challenges exist. + // Compress w2 layers to free memory during w1 commit (only when + // challenges exist; otherwise just drop them). let has_challenges = self.whir_for_witness.num_challenges > 0; - let w2_layers = if has_challenges { - Some(self.split_witness_builders.w2_layers) + let compressed_w2_layers = if has_challenges { + Some( + CompressedLayers::compress(self.split_witness_builders.w2_layers) + .context("While compressing w2 layers")?, + ) } else { drop(self.split_witness_builders.w2_layers); None @@ -158,6 +168,7 @@ impl Prove for Prover { debug!( witness_heap_bytes = witness.capacity() * size_of::>(), + compressed_r1cs_blob_bytes = compressed_r1cs.blob_len(), "component sizes after solve_w1" ); @@ -188,7 +199,10 @@ impl Prove for Prover { .context("While committing to w1")?; let commitments = if has_challenges { - let w2_layers = w2_layers.expect("w2_layers is Some whenever has_challenges is true"); + let w2_layers = compressed_w2_layers + .unwrap() + .decompress() + .context("While decompressing w2 layers")?; { let _s = info_span!("solve_w2").entered(); crate::r1cs::solve_witness_vec( @@ -220,6 +234,11 @@ impl Prove for Prover { vec![commitment_1] }; + // Decompress R1CS for the sumcheck and matrix operations. + let r1cs = compressed_r1cs + .decompress() + .context("While decompressing R1CS")?; + #[cfg(test)] r1cs.test_witness_satisfaction(&witness.iter().map(|w| w.unwrap()).collect::>()) .context("While verifying R1CS instance")?; diff --git a/provekit/prover/src/r1cs.rs b/provekit/prover/src/r1cs.rs index 21436c28f..c23134dde 100644 --- a/provekit/prover/src/r1cs.rs +++ b/provekit/prover/src/r1cs.rs @@ -1,18 +1,78 @@ -#[cfg(test)] -use provekit_common::R1CS; use { crate::witness::witness_builder::WitnessBuilderSolver, acir::native_types::WitnessMap, - anyhow::Result, + anyhow::{Context, Result}, provekit_common::{ utils::batch_inverse_montgomery, witness::{LayerType, LayeredWitnessBuilders, WitnessBuilder}, - FieldElement, NoirElement, TranscriptSponge, + FieldElement, NoirElement, TranscriptSponge, R1CS, }, tracing::instrument, whir::transcript::ProverState, }; +/// Serialized R1CS matrices held as a compact postcard blob. +/// +/// The R1CS is only needed during the sumcheck phase. Compressing it +/// into a serialized blob frees that memory during the commit phase, +/// then decompresses when the sumcheck begins. +pub struct CompressedR1CS { + num_constraints: usize, + num_witnesses: usize, + blob: Vec, +} + +/// Serialized witness builder layers held as a compact postcard blob. +/// +/// Same strategy as [`CompressedR1CS`]: the w2 layers are not needed +/// until challenge-dependent witness solving, so we compress them to +/// free memory during the w1 commit. +pub struct CompressedLayers { + blob: Vec, +} + +impl CompressedLayers { + pub fn compress(layers: LayeredWitnessBuilders) -> Result { + let blob = postcard::to_allocvec(&layers) + .context("LayeredWitnessBuilders serialization failed")?; + Ok(Self { blob }) + } + + pub fn decompress(self) -> Result { + postcard::from_bytes(&self.blob).context("LayeredWitnessBuilders deserialization failed") + } +} + +impl CompressedR1CS { + pub fn compress(r1cs: R1CS) -> Result { + let num_constraints = r1cs.num_constraints(); + let num_witnesses = r1cs.num_witnesses(); + let blob = postcard::to_allocvec(&r1cs).context("R1CS serialization failed")?; + drop(r1cs); + Ok(Self { + num_constraints, + num_witnesses, + blob, + }) + } + + pub fn decompress(self) -> Result { + postcard::from_bytes(&self.blob).context("R1CS deserialization failed") + } + + pub const fn num_constraints(&self) -> usize { + self.num_constraints + } + + pub const fn num_witnesses(&self) -> usize { + self.num_witnesses + } + + pub fn blob_len(&self) -> usize { + self.blob.len() + } +} + #[cfg(test)] pub trait R1CSSolver { fn test_witness_satisfaction(&self, witness: &[FieldElement]) -> anyhow::Result<()>; diff --git a/tooling/provekit-ffi/Cargo.toml b/tooling/provekit-ffi/Cargo.toml index 58a881587..950629d2d 100644 --- a/tooling/provekit-ffi/Cargo.toml +++ b/tooling/provekit-ffi/Cargo.toml @@ -9,7 +9,7 @@ homepage.workspace = true repository.workspace = true [lib] -crate-type = ["staticlib"] +crate-type = ["staticlib", "rlib"] [dependencies] # Workspace crates @@ -20,10 +20,12 @@ provekit-verifier.workspace = true # Noir language noirc_abi.workspace = true +noirc_artifacts.workspace = true # 3rd party anyhow.workspace = true serde.workspace = true +serde_json.workspace = true parking_lot.workspace = true [target.'cfg(unix)'.dependencies] diff --git a/tooling/provekit-ffi/src/ffi_allocator.rs b/tooling/provekit-ffi/src/ffi_allocator.rs index e6920306f..4b09a1383 100644 --- a/tooling/provekit-ffi/src/ffi_allocator.rs +++ b/tooling/provekit-ffi/src/ffi_allocator.rs @@ -79,6 +79,26 @@ fn load_dealloc_fn() -> Option { } } +#[inline(always)] +unsafe fn default_alloc(layout: Layout) -> *mut u8 { + std::alloc::System.alloc(layout) +} + +#[inline(always)] +unsafe fn default_dealloc(ptr: *mut u8, layout: Layout) { + std::alloc::System.dealloc(ptr, layout); +} + +#[inline(always)] +unsafe fn default_alloc_zeroed(layout: Layout) -> *mut u8 { + std::alloc::System.alloc_zeroed(layout) +} + +#[inline(always)] +unsafe fn default_realloc(ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + std::alloc::System.realloc(ptr, layout, new_size) +} + unsafe impl GlobalAlloc for FfiAllocator { #[inline(always)] unsafe fn alloc(&self, layout: Layout) -> *mut u8 { @@ -90,7 +110,7 @@ unsafe impl GlobalAlloc for FfiAllocator { // Fallback to callback or system allocator match load_alloc_fn() { Some(f) => f(layout.size(), layout.align()) as *mut u8, - None => std::alloc::System.alloc(layout), + None => default_alloc(layout), } } @@ -104,7 +124,7 @@ unsafe impl GlobalAlloc for FfiAllocator { // Fallback to callback or system allocator match load_dealloc_fn() { Some(f) => f(ptr as *mut c_void, layout.size(), layout.align()), - None => std::alloc::System.dealloc(ptr, layout), + None => default_dealloc(ptr, layout), } } @@ -124,7 +144,7 @@ unsafe impl GlobalAlloc for FfiAllocator { } ptr } - None => std::alloc::System.alloc_zeroed(layout), + None => default_alloc_zeroed(layout), } } @@ -149,7 +169,7 @@ unsafe impl GlobalAlloc for FfiAllocator { } new_ptr } - _ => std::alloc::System.realloc(ptr, layout, new_size), + _ => default_realloc(ptr, layout, new_size), } } } diff --git a/tooling/provekit-ffi/src/in_process.rs b/tooling/provekit-ffi/src/in_process.rs new file mode 100644 index 000000000..4fdaf78bb --- /dev/null +++ b/tooling/provekit-ffi/src/in_process.rs @@ -0,0 +1,131 @@ +//! Safe in-process helpers built on the same ProveKit implementation as the C +//! FFI entrypoints. + +use { + anyhow::{Context, Result}, + noirc_abi::{input_parser::Format, InputMap}, + noirc_artifacts::program::ProgramArtifact, + provekit_common::{NoirProof, NoirProofScheme, Prover, Verifier}, + provekit_prover::Prove, + provekit_r1cs_compiler::NoirProofSchemeBuilder, + provekit_verifier::Verify, +}; + +/// Ask the platform allocator to return free pages to the OS after a large +/// proof allocation burst. +pub fn trim_process_memory() { + #[cfg(any(target_os = "android", target_os = "linux"))] + unsafe { + type MallocTrim = unsafe extern "C" fn(libc::size_t) -> libc::c_int; + + let symbol = libc::dlsym(libc::RTLD_DEFAULT, c"malloc_trim".as_ptr().cast()); + if symbol.is_null() { + return; + } + let malloc_trim: MallocTrim = std::mem::transmute(symbol); + + // SAFETY: malloc_trim does not take ownership of Rust allocations. It + // only asks the process allocator to release unused free-list pages. + malloc_trim(0); + } +} + +/// Prepared proving and verification state for one Noir benchmark program. +#[derive(Clone)] +pub struct PreparedNoirProgram { + name: String, + prover: Prover, + verifier: Verifier, + input_map: InputMap, +} + +impl PreparedNoirProgram { + /// Return the R1CS size exposed by the prepared prover. + pub fn prover_size(&self) -> (usize, usize) { + self.prover.size() + } + + /// Return the number of R1CS constraints in the prepared verifier. + pub fn constraint_count(&self) -> usize { + self.verifier.r1cs.num_constraints() + } + + /// Return the number of parsed ABI input values. + pub fn input_count(&self) -> usize { + self.input_map.len() + } + + /// Generate and bind a proof to the matching verifier state. + pub fn prove(self) -> Result { + let proof = self + .prover + .prove(self.input_map) + .with_context(|| format!("while proving {} benchmark fixture", self.name))?; + + Ok(VerifiedNoirProgram { + name: self.name, + verifier: self.verifier, + proof, + }) + } + + /// Generate only the proof, dropping verifier-side state before proving. + pub fn prove_only(self) -> Result { + let Self { + name, + prover, + verifier, + input_map, + } = self; + + drop(verifier); + trim_process_memory(); + + prover + .prove(input_map) + .with_context(|| format!("while proving {name} benchmark fixture")) + } +} + +/// Verified-ready proof plus verifier state for one Noir benchmark program. +#[derive(Clone)] +pub struct VerifiedNoirProgram { + name: String, + verifier: Verifier, + proof: NoirProof, +} + +impl VerifiedNoirProgram { + /// Verify the proof against its matching verifier state. + pub fn verify(mut self) -> Result { + self.verifier + .verify(&self.proof) + .with_context(|| format!("while verifying {} benchmark fixture", self.name))?; + + Ok(self) + } +} + +/// Prepare a Noir program from an already-compiled artifact JSON string and a +/// TOML witness input string. +pub fn prepare_noir_program_from_json( + name: impl Into, + program_json: &str, + prover_toml: &str, +) -> Result { + let name = name.into(); + let program: ProgramArtifact = serde_json::from_str(program_json) + .with_context(|| format!("while deserializing {name} program artifact"))?; + let scheme = NoirProofScheme::from_program(program) + .with_context(|| format!("while preparing {name} noir proof scheme"))?; + let input_map = Format::Toml + .parse(prover_toml, &scheme.witness_generator.abi) + .with_context(|| format!("while parsing {name} prover inputs"))?; + + Ok(PreparedNoirProgram { + name, + prover: Prover::from_noir_proof_scheme(scheme.clone()), + verifier: Verifier::from_noir_proof_scheme(scheme), + input_map, + }) +} diff --git a/tooling/provekit-ffi/src/lib.rs b/tooling/provekit-ffi/src/lib.rs index 3a2f3feb5..c4eecfcf7 100644 --- a/tooling/provekit-ffi/src/lib.rs +++ b/tooling/provekit-ffi/src/lib.rs @@ -29,6 +29,7 @@ pub mod ffi; mod ffi_allocator; +pub mod in_process; pub mod mmap_allocator; mod serialization; pub mod types;