From 731e4d63f69442f2b0852ef338ec66a47c598739 Mon Sep 17 00:00:00 2001 From: dcbuild3r Date: Sun, 24 May 2026 10:26:02 +0200 Subject: [PATCH 1/4] Add compact mobench integration for main --- .github/scripts/merge_mobench_split_runs.py | 298 +++ .github/workflows/ci.yml | 6 + .github/workflows/mobile-bench-pr-auto.yml | 150 ++ .github/workflows/mobile-bench-pr-command.yml | 118 ++ .github/workflows/mobile-bench-reusable.yml | 1766 +++++++++++++++++ .github/workflows/mobile-bench.yml | 148 ++ Cargo.lock | 51 + Cargo.toml | 4 + bench-mobile/Cargo.toml | 31 + bench-mobile/build.rs | 104 + bench-mobile/scripts/generate-fixtures.sh | 35 + bench-mobile/src/examples.rs | 84 + bench-mobile/src/lib.rs | 529 +++++ bench-mobile/src/passport.rs | 212 ++ bench-mobile/tests/examples_smoke.rs | 27 + bench-mobile/tests/passport_smoke.rs | 40 + mobench.toml | 20 + .../tbs_1300/t_add_dsc_verify_1300.toml | 2 +- .../tbs_1300/t_add_id_data_1300.toml | 2 +- .../tbs_720/t_add_dsc_720.toml | 2 +- .../tbs_720/t_add_id_data_720.toml | 2 +- noir-examples/p256_bigcurve/Nargo.toml | 4 +- noir-examples/p256_bigcurve/src/main.nr | 10 +- tooling/provekit-ffi/Cargo.toml | 2 + tooling/provekit-ffi/src/ffi_allocator.rs | 28 +- tooling/provekit-ffi/src/in_process.rs | 131 ++ tooling/provekit-ffi/src/lib.rs | 1 + 27 files changed, 3788 insertions(+), 19 deletions(-) create mode 100644 .github/scripts/merge_mobench_split_runs.py create mode 100644 .github/workflows/mobile-bench-pr-auto.yml create mode 100644 .github/workflows/mobile-bench-pr-command.yml create mode 100644 .github/workflows/mobile-bench-reusable.yml create mode 100644 .github/workflows/mobile-bench.yml create mode 100644 bench-mobile/Cargo.toml create mode 100644 bench-mobile/build.rs create mode 100755 bench-mobile/scripts/generate-fixtures.sh create mode 100644 bench-mobile/src/examples.rs create mode 100644 bench-mobile/src/lib.rs create mode 100644 bench-mobile/src/passport.rs create mode 100644 bench-mobile/tests/examples_smoke.rs create mode 100644 bench-mobile/tests/passport_smoke.rs create mode 100644 mobench.toml create mode 100644 tooling/provekit-ffi/src/in_process.rs diff --git a/.github/scripts/merge_mobench_split_runs.py b/.github/scripts/merge_mobench_split_runs.py new file mode 100644 index 000000000..86d019a1b --- /dev/null +++ b/.github/scripts/merge_mobench_split_runs.py @@ -0,0 +1,298 @@ +#!/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]], target: str) -> 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", target) + resources.setdefault("memory_process", "isolated_worker") + return resources + + +def merge_reports( + reports: list[tuple[Path, dict[str, Any]]], + function: str, + iterations: int, + warmup: int, + target: str, +) -> 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, target) + + 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": target, + "device_summaries": [{"device": device, "benchmarks": [summary_benchmark]}], + } + base["spec"] = { + **(copy.deepcopy(base.get("spec") or {})), + "name": function, + "iterations": iterations, + "warmup": warmup, + } + base.setdefault("ci", {})[f"split_{target}_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, target: str) -> 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}", + f"- Target: {target.title()}", + 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") + parser.add_argument("--device") + parser.add_argument("--iterations", type=int, required=True) + parser.add_argument("--warmup", type=int, required=True) + parser.add_argument("--target", choices=["android", "ios"], default="android") + args = parser.parse_args() + + reports = load_reports(args.samples_dir) + inferred_device, inferred_benchmark = single_benchmark(reports[0][1]) + function = args.function or inferred_benchmark.get("function") or inferred_benchmark.get("spec", {}).get("name") + if not function: + raise SystemExit("unable to infer benchmark function; pass --function") + device_arg = args.device or inferred_device + + merged = merge_reports(reports, function, args.iterations, args.warmup, args.target) + 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, device_arg, device, summary_benchmark, args.warmup, args.target) + + print(f"Merged {args.iterations} split sample(s) for {function} on {device_arg}") + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c13aa272..42c666276 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,12 @@ jobs: with: channel: nightly-2026-03-04 cache-base: main + - name: Setup Noir + uses: noir-lang/noirup@v0.1.2 + with: + toolchain: v1.0.0-beta.19 + - name: Generate mobile benchmark Noir artifacts + run: bench-mobile/scripts/generate-fixtures.sh - name: Build run: cargo build --all-targets --all-features --verbose - name: Run tests diff --git a/.github/workflows/mobile-bench-pr-auto.yml b/.github/workflows/mobile-bench-pr-auto.yml new file mode 100644 index 000000000..74dc11130 --- /dev/null +++ b/.github/workflows/mobile-bench-pr-auto.yml @@ -0,0 +1,150 @@ +name: Mobile Bench PR Auto + +on: + push: + branches: + - dcbuild3r/main-mobench-fixtures + 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..7100ab031 --- /dev/null +++ b/.github/workflows/mobile-bench-reusable.yml @@ -0,0 +1,1766 @@ +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 (${{ matrix.ios_shard.function_slug }} / ${{ matrix.ios_shard.device_slug }} / batch ${{ matrix.ios_batch }}) + if: inputs.platform == 'ios' || inputs.platform == 'both' + runs-on: macos-15 + environment: Browserstack + concurrency: + group: mobench-browserstack-device-cloud + cancel-in-progress: false + strategy: + fail-fast: false + max-parallel: 1 + matrix: + ios_shard: + - function: bench_mobile::bench_passport_complete_age_check_prove + function_slug: monolithic + device: iPhone SE 2022-15 + device_slug: iphone-se-2022 + - function: bench_mobile::bench_passport_complete_age_check_prove + function_slug: monolithic + device: iPhone 14-16 + device_slug: iphone-14 + - function: bench_mobile::bench_passport_complete_age_check_prove + function_slug: monolithic + device: iPhone 16 Pro Max-18 + device_slug: iphone-16-pro-max + - function: bench_mobile::bench_passport_fragmented_age_check_prove + function_slug: fragmented + device: iPhone SE 2022-15 + device_slug: iphone-se-2022 + - function: bench_mobile::bench_passport_fragmented_age_check_prove + function_slug: fragmented + device: iPhone 14-16 + device_slug: iphone-14 + - function: bench_mobile::bench_passport_fragmented_age_check_prove + function_slug: fragmented + device: iPhone 16 Pro Max-18 + device_slug: iphone-16-pro-max + - function: bench_mobile::bench_oprf_prove + function_slug: oprf + device: iPhone SE 2022-15 + device_slug: iphone-se-2022 + - function: bench_mobile::bench_oprf_prove + function_slug: oprf + device: iPhone 14-16 + device_slug: iphone-14 + - function: bench_mobile::bench_oprf_prove + function_slug: oprf + device: iPhone 16 Pro Max-18 + device_slug: iphone-16-pro-max + ios_batch: [1, 2, 3, 4, 5] + 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.19 + + - 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: ${{ matrix.ios_shard.function }} + IOS_MATRIX_DEVICE: ${{ matrix.ios_shard.device }} + IOS_BATCH_INDEX: ${{ matrix.ios_batch }} + 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_MATRIX_DEVICE}" + 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_min_os_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" + } + + ios_devices=() + IFS=',' read -r -a raw_ios_devices <<<"${IOS_MATRIX_DEVICE}" + for ios_device in "${raw_ios_devices[@]}"; do + ios_device="$(echo "$ios_device" | xargs)" + if [[ -n "$ios_device" ]]; then + ios_devices+=("${ios_device}") + fi + done + if [ "${#ios_devices[@]}" -eq 0 ]; then + echo "::error::No iOS devices resolved from '${IOS_DEVICE_SPECS}'" + exit 1 + fi + + for benchmark_function in "${benchmark_functions[@]}"; do + function_iterations="${ITERATIONS}" + function_warmup="${WARMUP}" + + function_slug="$(tr -c '[:alnum:]' '_' <<<"${benchmark_function}" | sed 's/_*$//')" + for ios_device in "${ios_devices[@]}"; do + device_slug="$(tr -c '[:alnum:]' '_' <<<"${ios_device}" | sed 's/_*$//')" + split_ios_samples="false" + if [[ "${function_iterations}" =~ ^[0-9]+$ ]] && [ "${function_iterations}" -ge 100 ]; then + split_ios_samples="true" + fi + + if [[ "${split_ios_samples}" == "true" ]]; then + result_output_dir="target/mobench/ci/ios/${function_slug}/${device_slug}" + split_output_dir="${result_output_dir}/split" + warmup_output_base="target/mobench/split-warmup/ios/${function_slug}/${device_slug}" + rm -rf "${result_output_dir}" "target/browserstack/ios/${function_slug}/${device_slug}" "${warmup_output_base}" "target/browserstack/ios-warmup/${function_slug}/${device_slug}" + mkdir -p "${split_output_dir}" "${warmup_output_base}" + split_failed="false" + ios_split_batch_size=20 + ios_split_fetch_timeout_secs=7200 + + run_ios_split_invocation() { + local kind="$1" + local index="$2" + local iterations="$3" + local warmup="$4" + local output_dir="$5" + local fetch_output_dir="$6" + local split_max_attempts="1" + 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}" + + echo "mobench ios split ${kind} ${index} attempt ${attempt}/${split_max_attempts}: ${benchmark_function} on ${ios_device} (${iterations} iterations, ${warmup} warmup)" + set +e + cargo-mobench ci run \ + --target ios \ + --function "${benchmark_function}" \ + --iterations "${iterations}" \ + --warmup "${warmup}" \ + --devices "${ios_device}" \ + --crate-path "$CRATE_PATH" \ + $RELEASE_FLAG \ + --ios-deployment-target "${IOS_DEPLOYMENT_TARGET}" \ + --ios-runner uikit-legacy \ + --fetch \ + --fetch-timeout-secs "${ios_split_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 is_ios_min_os_schedule_failure "$log_path"; then + echo "::error::iOS device ${ios_device} requires a newer app/test-suite target in BrowserStack or is unavailable." + return "$status" + fi + if [ "$attempt" -ge "$split_max_attempts" ] || ! is_transient_fetch_failure "$log_path"; then + 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 iOS build ${build_id}; retrying ${benchmark_function} on ${ios_device} (${kind} ${index}, attempt ${attempt}/${split_max_attempts})" + else + echo "::warning::Transient BrowserStack fetch failure for split iOS benchmark; retrying ${benchmark_function} on ${ios_device} (${kind} ${index}, attempt ${attempt}/${split_max_attempts})" + fi + sleep $((attempt * 15)) + done + } + + batch_index="${IOS_BATCH_INDEX}" + sample_start=$(((batch_index - 1) * ios_split_batch_size + 1)) + if [ "${sample_start}" -gt "${function_iterations}" ]; then + echo "Skipping iOS split batch ${batch_index}; ${function_iterations} iteration(s) require fewer batches." + continue + fi + + remaining=$((function_iterations - sample_start + 1)) + batch_iterations="${ios_split_batch_size}" + if [ "${remaining}" -lt "${batch_iterations}" ]; then + batch_iterations="${remaining}" + fi + batch_warmup=0 + if [ "${batch_index}" -eq 1 ]; then + batch_warmup="${function_warmup}" + fi + + if ! run_ios_split_invocation "sample" "${batch_index}" "${batch_iterations}" "${batch_warmup}" "${split_output_dir}/sample-${batch_index}" "target/browserstack/ios/${function_slug}/${device_slug}/sample-${batch_index}"; then + split_failed="true" + fi + + if [[ "${split_failed}" == "true" ]]; then + exit 1 + fi + + continue + fi + + if [ "${IOS_BATCH_INDEX}" -ne 1 ]; then + echo "Skipping non-split iOS batch ${IOS_BATCH_INDEX}; non-100-run jobs only use batch 1." + continue + fi + + attempt=1 + while true; do + attempt_log="${log_dir}/${function_slug}-${device_slug}-attempt-${attempt}.log" + fetch_output_dir="target/browserstack/ios/${function_slug}/${device_slug}" + result_output_dir="target/mobench/ci/ios/${function_slug}/${device_slug}" + rm -rf "${fetch_output_dir}" + mkdir -p "${fetch_output_dir}" "${result_output_dir}" + + echo "mobench ios ${benchmark_function} attempt ${attempt}/${max_attempts} on ${ios_device}" + set +e + cargo-mobench ci run \ + --target ios \ + --function "${benchmark_function}" \ + --iterations "${function_iterations}" \ + --warmup "${function_warmup}" \ + --devices "${ios_device}" \ + --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_min_os_schedule_failure "$attempt_log"; then + echo "::error::iOS device ${ios_device} requires a newer app/test-suite target in BrowserStack or is unavailable." + exit "$status" + 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} on ${ios_device}" + else + echo "::warning::Transient BrowserStack fetch failure for iOS; retrying ${benchmark_function} on ${ios_device}" + fi + + attempt=$((attempt + 1)) + sleep "$retry_sleep_secs" + done + done + done + + - name: Upload iOS results + if: always() + uses: actions/upload-artifact@v4 + with: + name: mobench-results-ios-${{ matrix.ios_shard.function_slug }}-${{ matrix.ios_shard.device_slug }}-batch-${{ matrix.ios_batch }} + path: | + caller/target/mobench/ci/ios/** + caller/target/browserstack/ios/** + caller/target/browserstack/ios-warmup/** + caller/target/mobench/retry-logs/ios/** + caller/target/mobench/split-warmup/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.19 + + - 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: + pattern: mobench-results-ios-* + path: results/ios + merge-multiple: true + + - 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: Merge split iOS shard results + if: always() + shell: bash + env: + ITERATIONS: ${{ inputs.iterations }} + WARMUP: ${{ inputs.warmup }} + run: | + set -euo pipefail + if [ ! -d "results/ios/mobench/ci/ios" ]; then + exit 0 + fi + + while IFS= read -r -d '' split_dir; do + result_output_dir="$(dirname "${split_dir}")" + sample_count=$(find "${split_dir}" -mindepth 1 -maxdepth 1 -type d -name 'sample-*' | wc -l | tr -d ' ') + if [ "${sample_count}" -eq 0 ]; then + continue + fi + + python3 caller/.github/scripts/merge_mobench_split_runs.py \ + --samples-dir "${split_dir}" \ + --output-dir "${result_output_dir}" \ + --iterations "${ITERATIONS}" \ + --warmup "${WARMUP}" \ + --target ios + done < <(find results/ios/mobench/ci/ios -type d -name split -print0) + + - 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_count + summary_count=$(find "${results_dir}" -type f -name summary.json | wc -l | tr -d ' ') + if [ "${summary_count}" -eq 0 ]; then + echo "::warning::No ${platform} summary.json found under ${results_dir}" + return 0 + fi + + local csv_count + csv_count=$(find "${results_dir}" -type f -name results.csv | wc -l | tr -d ' ') + if [ "${csv_count}" -eq 0 ]; then + echo "::warning::No ${platform} results.csv found under ${results_dir}" + return 0 + fi + + mkdir -p "rendered/${platform}" + cargo-mobench ci summarize \ + --results-dir "${results_dir}" \ + --output-format markdown \ + --output-file "rendered/${platform}/summary.md" + + while IFS= read -r failure_md; do + { + echo "" + cat "${failure_md}" + } >> "rendered/${platform}/summary.md" + done < <(find "${results_dir}" -type f -name failure.md | sort) + } + + 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 f93dc8543..d013955e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -697,6 +697,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" @@ -2725,6 +2740,15 @@ dependencies = [ "smallvec", ] +[[package]] +name = "inventory" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" +dependencies = [ + "rustversion", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -3265,6 +3289,31 @@ dependencies = [ "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 = "nargo" version = "1.0.0-beta.19" @@ -4666,12 +4715,14 @@ dependencies = [ "nargo_cli", "nargo_toml", "noirc_abi", + "noirc_artifacts", "noirc_driver", "parking_lot", "provekit-common", "provekit-prover", "provekit-r1cs-compiler", "provekit-verifier", + "serde_json", "tempfile", ] diff --git a/Cargo.toml b/Cargo.toml index 73d5ac541..8523646e7 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"] } num-bigint = "0.4" paste = "1.0.15" postcard = { version = "1.1.1", features = ["use-std"] } @@ -150,6 +153,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" # Version-anchored: acvm_blackbox_solver (noir beta.19) requires keccak = "0.2.0-rc.0" # and calls keccak::f1600(), which was removed in keccak 0.2.0 stable. Pinning to 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/benchmark-inputs/tbs_1300/t_add_dsc_verify_1300.toml b/noir-examples/noir-passport/merkle_age_check/benchmark-inputs/tbs_1300/t_add_dsc_verify_1300.toml index 847d76156..e24296055 100644 --- a/noir-examples/noir-passport/merkle_age_check/benchmark-inputs/tbs_1300/t_add_dsc_verify_1300.toml +++ b/noir-examples/noir-passport/merkle_age_check/benchmark-inputs/tbs_1300/t_add_dsc_verify_1300.toml @@ -6,7 +6,7 @@ country = "UTO" state1 = [2255395565, 3984421451, 1880533823, 1801696719, 786056496, 1504766900, 4275076418, 57901920] tbs_certificate = [48, 130, 3, 78, 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, 52, 56, 48, 48, 90, 23, 13, 51, 49, 48, 51, 50, 52, 49, 51, 52, 56, 48, 48, 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, 130, 1, 100, 48, 130, 1, 96, 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, 48, 130, 1, 16, 6, 9, 43, 6, 1, 4, 1, 134, 141, 31, 1, 4, 130, 1, 1, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] tbs_certificate_len = 850 -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] +csc_pubkey_redc_param = [85, 174, 126, 117, 33, 220, 72, 0, 109, 30, 197, 185, 103, 14, 48, 128, 1, 71, 50, 56, 40, 168, 230, 184, 224, 196, 82, 184, 162, 160, 53, 185, 220, 250, 11, 57, 198, 14, 141, 23, 98, 80, 210, 165, 146, 5, 203, 252, 187, 156, 244, 57, 67, 46, 33, 120, 203, 8, 133, 252, 82, 131, 169, 28, 83, 36, 35, 159, 124, 3, 0, 154, 43, 162, 241, 149, 17, 157, 153, 68, 109, 56, 149, 128, 46, 28, 244, 50, 120, 150, 55, 94, 92, 102, 194, 28, 166, 22, 141, 199, 118, 134, 191, 136, 37, 199, 243, 151, 188, 192, 222, 136, 134, 203, 129, 120, 74, 135, 114, 234, 140, 42, 21, 85, 253, 41, 125, 43, 2, 145, 23, 177, 229, 127, 129, 206, 214, 166, 113, 230, 134, 209, 252, 244, 105, 197, 4, 143, 197, 93, 14, 96, 162, 128, 118, 251, 229, 222, 200, 161, 143, 27, 121, 154, 137, 18, 42, 167, 183, 7, 30, 94, 125, 65, 216, 82, 53, 135, 129, 51, 80, 117, 67, 184, 131, 170, 178, 94, 54, 27, 142, 196, 245, 168, 37, 167, 10, 87, 162, 174, 150, 31, 208, 99, 91, 84, 113, 205, 17, 46, 131, 26, 5, 39, 184, 236, 236, 16, 181, 151, 175, 115, 131, 128, 21, 48, 55, 106, 38, 246, 184, 208, 155, 3, 213, 254, 41, 69, 131, 254, 137, 220, 179, 75, 221, 9, 140, 15, 41, 184, 106, 184, 110, 116, 61, 85, 69, 206, 136, 143, 101, 37, 82, 47, 27, 59, 53, 119, 119, 62, 217, 248, 83, 78, 200, 95, 161, 127, 247, 243, 251, 78, 62, 86, 9, 153, 20, 191, 154, 52, 93, 174, 80, 141, 137, 85, 137, 191, 185, 86, 81, 191, 237, 79, 113, 98, 113, 68, 111, 16, 32, 21, 11, 97, 188, 15, 139, 81, 66, 92, 154, 146, 178, 245, 195, 131, 133, 138, 149, 90, 208, 124, 131, 231, 41, 255, 210, 57, 252, 71, 188, 64, 164, 6, 253, 198, 24, 73, 11, 239, 143, 249, 36, 214, 209, 160, 110, 20, 131, 27, 106, 127, 136, 129, 62, 33, 204, 209, 187, 203, 191, 49, 182, 104, 118, 209, 86, 59, 210, 129, 104, 56, 151, 178, 126, 11, 150, 164, 44, 150, 16, 148, 197, 243, 135, 58, 147, 41, 120, 136, 32, 20, 196, 224, 70, 173, 7, 77, 248, 169, 180, 250, 194, 17, 172, 250, 250, 55, 88, 47, 100, 24, 211, 26, 118, 212, 89, 175, 215, 231, 120, 17, 28, 252, 217, 160, 94, 174, 210, 12, 67, 152, 93, 120, 156, 246, 87, 48, 60, 168, 30, 238, 76, 148, 221, 14, 242, 79, 11, 250, 107, 5, 127, 142, 139, 96, 13, 253, 211, 225, 205, 229, 250, 195, 246, 188, 31, 214, 190, 7, 249, 26, 92, 146, 187, 174, 178, 123, 211, 57, 222, 227, 156, 4, 58, 138, 98, 125, 134, 33, 75, 97, 46, 132, 147, 64, 236, 248, 52, 48, 141, 75, 176] dsc_signature = [1, 90, 47, 176, 125, 161, 115, 42, 228, 134, 123, 122, 5, 10, 192, 251, 21, 176, 234, 130, 181, 159, 129, 112, 177, 142, 115, 49, 83, 162, 150, 234, 133, 233, 115, 90, 20, 113, 84, 60, 187, 88, 115, 7, 236, 156, 43, 41, 97, 196, 158, 176, 156, 61, 214, 171, 84, 234, 120, 38, 152, 93, 137, 186, 244, 89, 139, 161, 114, 156, 16, 105, 104, 254, 128, 219, 168, 148, 238, 79, 117, 131, 9, 121, 107, 114, 88, 246, 52, 220, 17, 114, 106, 6, 103, 56, 105, 75, 39, 120, 64, 136, 237, 83, 122, 147, 231, 96, 120, 145, 199, 203, 17, 76, 243, 108, 145, 6, 53, 178, 217, 197, 42, 29, 45, 166, 18, 163, 37, 198, 170, 204, 246, 145, 28, 88, 220, 217, 127, 129, 200, 243, 202, 201, 100, 203, 21, 26, 82, 136, 221, 195, 134, 115, 215, 35, 122, 92, 11, 126, 239, 123, 69, 103, 185, 5, 53, 172, 70, 105, 92, 242, 116, 163, 110, 14, 150, 211, 129, 135, 95, 219, 68, 18, 205, 169, 48, 179, 202, 174, 150, 37, 254, 211, 150, 59, 44, 172, 4, 87, 133, 57, 206, 135, 138, 187, 101, 245, 146, 151, 9, 54, 228, 40, 0, 245, 140, 211, 134, 38, 50, 174, 82, 124, 107, 54, 255, 30, 31, 40, 201, 20, 119, 75, 74, 242, 187, 44, 218, 182, 218, 185, 153, 221, 170, 2, 145, 206, 75, 46, 206, 164, 0, 219, 171, 204, 169, 20, 212, 145, 93, 26, 101, 101, 222, 26, 93, 215, 141, 207, 218, 178, 94, 14, 54, 2, 45, 172, 249, 227, 172, 222, 105, 152, 120, 121, 246, 138, 144, 112, 123, 60, 250, 244, 40, 29, 247, 190, 99, 79, 54, 0, 240, 119, 110, 230, 129, 88, 95, 219, 196, 159, 249, 48, 236, 220, 232, 3, 177, 8, 34, 210, 101, 147, 135, 161, 82, 125, 47, 216, 138, 186, 108, 74, 178, 129, 57, 227, 132, 49, 251, 45, 248, 119, 44, 147, 173, 178, 29, 150, 89, 241, 165, 37, 22, 157, 75, 225, 2, 237, 3, 55, 220, 104, 206, 8, 164, 37, 217, 22, 186, 239, 230, 194, 49, 93, 213, 191, 141, 79, 207, 181, 221, 34, 59, 234, 173, 228, 10, 146, 250, 117, 36, 188, 147, 234, 126, 79, 165, 228, 43, 95, 202, 48, 170, 84, 5, 91, 7, 230, 92, 108, 44, 198, 200, 156, 24, 203, 65, 68, 120, 65, 209, 200, 217, 182, 140, 39, 101, 245, 218, 21, 193, 169, 153, 47, 112, 164, 252, 99, 62, 99, 201, 25, 13, 103, 97, 59, 198, 7, 193, 40, 148, 201, 184, 75, 25, 98, 54, 243, 239, 7, 2, 15, 30, 104, 73, 40, 61, 149, 233, 207, 151, 75, 135, 0, 108, 124, 158, 9, 14, 134, 151, 82, 138, 235, 199, 127, 184, 140, 178, 172, 224, 252, 94, 15, 254, 128, 230, 69, 139, 195, 122, 107, 172, 243, 151, 38, 66, 196, 210, 123] exponent = 65537 salt_out = "0x2" \ No newline at end of file diff --git a/noir-examples/noir-passport/merkle_age_check/benchmark-inputs/tbs_1300/t_add_id_data_1300.toml b/noir-examples/noir-passport/merkle_age_check/benchmark-inputs/tbs_1300/t_add_id_data_1300.toml index dc1b77129..bd04c8eeb 100644 --- a/noir-examples/noir-passport/merkle_age_check/benchmark-inputs/tbs_1300/t_add_id_data_1300.toml +++ b/noir-examples/noir-passport/merkle_age_check/benchmark-inputs/tbs_1300/t_add_id_data_1300.toml @@ -3,7 +3,7 @@ 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_redc_param = [113, 123, 99, 54, 11, 90, 238, 216, 235, 67, 146, 126, 2, 54, 79, 213, 19, 47, 178, 5, 142, 49, 179, 79, 215, 25, 30, 192, 11, 19, 196, 235, 116, 148, 219, 209, 118, 14, 83, 5, 93, 228, 154, 242, 59, 16, 17, 164, 104, 150, 90, 82, 99, 55, 173, 250, 225, 117, 164, 224, 176, 76, 230, 113, 42, 68, 211, 36, 217, 111, 104, 4, 105, 175, 111, 28, 112, 40, 228, 128, 91, 14, 12, 232, 186, 148, 230, 212, 214, 22, 219, 150, 208, 23, 149, 158, 178, 237, 128, 172, 56, 18, 95, 30, 32, 215, 131, 30, 157, 71, 194, 210, 187, 249, 95, 255, 191, 104, 6, 168, 33, 250, 244, 1, 77, 246, 182, 253, 80, 212, 117, 65, 96, 192, 236, 203, 206, 115, 116, 5, 68, 30, 51, 12, 113, 251, 13, 99, 139, 130, 54, 7, 115, 202, 247, 100, 64, 178, 142, 107, 220, 247, 183, 84, 227, 48, 59, 31, 237, 186, 45, 214, 56, 67, 169, 208, 189, 75, 137, 96, 160, 61, 161, 40, 48, 195, 131, 149, 0, 18, 116, 5, 243, 44, 206, 214, 255, 10, 85, 199, 132, 138, 183, 179, 56, 89, 66, 246, 214, 121, 147, 224, 240, 241, 18, 118, 165, 16, 107, 151, 138, 94, 212, 159, 20, 204, 206, 175, 22, 11, 19, 109, 206, 69, 81, 22, 119, 221, 30, 55, 25, 183, 111, 254, 87, 144, 76, 92, 226, 189, 237, 175, 3, 110, 190, 8, 243] 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] 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 index 995956008..e817a9766 100644 --- 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 @@ -3,7 +3,7 @@ csc_key_ne_hash = "0x1ca4af41d370d02b729b6a63b4aea3cf29242ad76ac8420c144d7558072 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] +csc_pubkey_redc_param = [85, 174, 126, 117, 33, 220, 72, 0, 109, 30, 197, 185, 103, 14, 48, 128, 1, 71, 50, 56, 40, 168, 230, 184, 224, 196, 82, 184, 162, 160, 53, 185, 220, 250, 11, 57, 198, 14, 141, 23, 98, 80, 210, 165, 146, 5, 203, 252, 187, 156, 244, 57, 67, 46, 33, 120, 203, 8, 133, 252, 82, 131, 169, 28, 83, 36, 35, 159, 124, 3, 0, 154, 43, 162, 241, 149, 17, 157, 153, 68, 109, 56, 149, 128, 46, 28, 244, 50, 120, 150, 55, 94, 92, 102, 194, 28, 166, 22, 141, 199, 118, 134, 191, 136, 37, 199, 243, 151, 188, 192, 222, 136, 134, 203, 129, 120, 74, 135, 114, 234, 140, 42, 21, 85, 253, 41, 125, 43, 2, 145, 23, 177, 229, 127, 129, 206, 214, 166, 113, 230, 134, 209, 252, 244, 105, 197, 4, 143, 197, 93, 14, 96, 162, 128, 118, 251, 229, 222, 200, 161, 143, 27, 121, 154, 137, 18, 42, 167, 183, 7, 30, 94, 125, 65, 216, 82, 53, 135, 129, 51, 80, 117, 67, 184, 131, 170, 178, 94, 54, 27, 142, 196, 245, 168, 37, 167, 10, 87, 162, 174, 150, 31, 208, 99, 91, 84, 113, 205, 17, 46, 131, 26, 5, 39, 184, 236, 236, 16, 181, 151, 175, 115, 131, 128, 21, 48, 55, 106, 38, 246, 184, 208, 155, 3, 213, 254, 41, 69, 131, 254, 137, 220, 179, 75, 221, 9, 140, 15, 41, 184, 106, 184, 110, 116, 61, 85, 69, 206, 136, 143, 101, 37, 82, 47, 27, 59, 53, 119, 119, 62, 217, 248, 83, 78, 200, 95, 161, 127, 247, 243, 251, 78, 62, 86, 9, 153, 20, 191, 154, 52, 93, 174, 80, 141, 137, 85, 137, 191, 185, 86, 81, 191, 237, 79, 113, 98, 113, 68, 111, 16, 32, 21, 11, 97, 188, 15, 139, 81, 66, 92, 154, 146, 178, 245, 195, 131, 133, 138, 149, 90, 208, 124, 131, 231, 41, 255, 210, 57, 252, 71, 188, 64, 164, 6, 253, 198, 24, 73, 11, 239, 143, 249, 36, 214, 209, 160, 110, 20, 131, 27, 106, 127, 136, 129, 62, 33, 204, 209, 187, 203, 191, 49, 182, 104, 118, 209, 86, 59, 210, 129, 104, 56, 151, 178, 126, 11, 150, 164, 44, 150, 16, 148, 197, 243, 135, 58, 147, 41, 120, 136, 32, 20, 196, 224, 70, 173, 7, 77, 248, 169, 180, 250, 194, 17, 172, 250, 250, 55, 88, 47, 100, 24, 211, 26, 118, 212, 89, 175, 215, 231, 120, 17, 28, 252, 217, 160, 94, 174, 210, 12, 67, 152, 93, 120, 156, 246, 87, 48, 60, 168, 30, 238, 76, 148, 221, 14, 242, 79, 11, 250, 107, 5, 127, 142, 139, 96, 13, 253, 211, 225, 205, 229, 250, 195, 246, 188, 31, 214, 190, 7, 249, 26, 92, 146, 187, 174, 178, 123, 211, 57, 222, 227, 156, 4, 58, 138, 98, 125, 134, 33, 75, 97, 46, 132, 147, 64, 236, 248, 52, 48, 141, 75, 176] 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 index 286711b2c..f75cfa27e 100644 --- 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 @@ -3,7 +3,7 @@ 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_redc_param = [113, 123, 99, 54, 11, 90, 238, 216, 235, 67, 146, 126, 2, 54, 79, 213, 19, 47, 178, 5, 142, 49, 179, 79, 215, 25, 30, 192, 11, 19, 196, 235, 116, 148, 219, 209, 118, 14, 83, 5, 93, 228, 154, 242, 59, 16, 17, 164, 104, 150, 90, 82, 99, 55, 173, 250, 225, 117, 164, 224, 176, 76, 230, 113, 42, 68, 211, 36, 217, 111, 104, 4, 105, 175, 111, 28, 112, 40, 228, 128, 91, 14, 12, 232, 186, 148, 230, 212, 214, 22, 219, 150, 208, 23, 149, 158, 178, 237, 128, 172, 56, 18, 95, 30, 32, 215, 131, 30, 157, 71, 194, 210, 187, 249, 95, 255, 191, 104, 6, 168, 33, 250, 244, 1, 77, 246, 182, 253, 80, 212, 117, 65, 96, 192, 236, 203, 206, 115, 116, 5, 68, 30, 51, 12, 113, 251, 13, 99, 139, 130, 54, 7, 115, 202, 247, 100, 64, 178, 142, 107, 220, 247, 183, 84, 227, 48, 59, 31, 237, 186, 45, 214, 56, 67, 169, 208, 189, 75, 137, 96, 160, 61, 161, 40, 48, 195, 131, 149, 0, 18, 116, 5, 243, 44, 206, 214, 255, 10, 85, 199, 132, 138, 183, 179, 56, 89, 66, 246, 214, 121, 147, 224, 240, 241, 18, 118, 165, 16, 107, 151, 138, 94, 212, 159, 20, 204, 206, 175, 22, 11, 19, 109, 206, 69, 81, 22, 119, 221, 30, 55, 25, 183, 111, 254, 87, 144, 76, 92, 226, 189, 237, 175, 3, 110, 190, 8, 243] 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] diff --git a/noir-examples/p256_bigcurve/Nargo.toml b/noir-examples/p256_bigcurve/Nargo.toml index c9df7a53a..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.9.2", git = "https://github.com/noir-lang/noir-bignum" } -bigcurve = { tag = "v0.13.2", 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 a6e25d439..4628196bc 100644 --- a/noir-examples/p256_bigcurve/src/main.nr +++ b/noir-examples/p256_bigcurve/src/main.nr @@ -4,13 +4,7 @@ use bigcurve::{ }; use bignum::BigNum; -fn main( - hashed_message: [u8; 32], - pub_key_x: [u8; 32], - pub_key_y: [u8; 32], - signature: [u8; 64], - r_point_y: [u8; 32], -) { +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: Secp256r1_Fq::from_be_bytes(pub_key_x), @@ -41,7 +35,5 @@ fn main( let s_p = Secp256r1Scalar::from_bignum(r / s); let r_point = Secp256r1::evaluate_linear_expression([gen, public], [s_g, s_p], []); - assert(!r_point.is_infinity); assert(r_point.x == r_x); - assert(r_point.y == Secp256r1_Fq::from_be_bytes(r_point_y)); } diff --git a/tooling/provekit-ffi/Cargo.toml b/tooling/provekit-ffi/Cargo.toml index ccfef3330..3adcd69d0 100644 --- a/tooling/provekit-ffi/Cargo.toml +++ b/tooling/provekit-ffi/Cargo.toml @@ -21,7 +21,9 @@ provekit-verifier = { workspace = true } # 3rd party anyhow.workspace = true noirc_abi.workspace = true +noirc_artifacts.workspace = true parking_lot.workspace = true +serde_json.workspace = true [target.'cfg(unix)'.dependencies] libc = "0.2" diff --git a/tooling/provekit-ffi/src/ffi_allocator.rs b/tooling/provekit-ffi/src/ffi_allocator.rs index 181c33dd0..d3f739989 100644 --- a/tooling/provekit-ffi/src/ffi_allocator.rs +++ b/tooling/provekit-ffi/src/ffi_allocator.rs @@ -73,6 +73,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 { @@ -84,7 +104,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), } } @@ -98,7 +118,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), } } @@ -118,7 +138,7 @@ unsafe impl GlobalAlloc for FfiAllocator { } ptr } - None => std::alloc::System.alloc_zeroed(layout), + None => default_alloc_zeroed(layout), } } @@ -143,7 +163,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..d60626d77 --- /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::{HashConfig, NoirProof, Prover, Verifier}, + provekit_prover::Prove, + provekit_r1cs_compiler::NoirCompiler, + 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 = NoirCompiler::from_program(program, HashConfig::default()) + .with_context(|| format!("while preparing {name} noir proof scheme"))?; + let input_map = Format::Toml + .parse(prover_toml, scheme.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 d41f0b95b..f0084a5cf 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; pub mod types; pub mod utils; From e14fa60beadc25bf1e7d238c9bdbf8f6237fa9f2 Mon Sep 17 00:00:00 2001 From: dcbuild3r Date: Sun, 24 May 2026 19:46:30 +0200 Subject: [PATCH 2/4] Trigger mobile benchmarks [mobench:both] From 0e319e3613261f4c5c45d9f6cfca4337ba2fff84 Mon Sep 17 00:00:00 2001 From: dcbuild3r Date: Tue, 9 Jun 2026 16:51:12 -0400 Subject: [PATCH 3/4] Consolidate mobile bench PR checks --- .github/workflows/mobile-bench-reusable.yml | 157 ++++---------------- 1 file changed, 33 insertions(+), 124 deletions(-) diff --git a/.github/workflows/mobile-bench-reusable.yml b/.github/workflows/mobile-bench-reusable.yml index 7100ab031..57ee8d830 100644 --- a/.github/workflows/mobile-bench-reusable.yml +++ b/.github/workflows/mobile-bench-reusable.yml @@ -111,55 +111,13 @@ env: jobs: ios: - name: iOS BrowserStack benchmark (${{ matrix.ios_shard.function_slug }} / ${{ matrix.ios_shard.device_slug }} / batch ${{ matrix.ios_batch }}) + 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 - strategy: - fail-fast: false - max-parallel: 1 - matrix: - ios_shard: - - function: bench_mobile::bench_passport_complete_age_check_prove - function_slug: monolithic - device: iPhone SE 2022-15 - device_slug: iphone-se-2022 - - function: bench_mobile::bench_passport_complete_age_check_prove - function_slug: monolithic - device: iPhone 14-16 - device_slug: iphone-14 - - function: bench_mobile::bench_passport_complete_age_check_prove - function_slug: monolithic - device: iPhone 16 Pro Max-18 - device_slug: iphone-16-pro-max - - function: bench_mobile::bench_passport_fragmented_age_check_prove - function_slug: fragmented - device: iPhone SE 2022-15 - device_slug: iphone-se-2022 - - function: bench_mobile::bench_passport_fragmented_age_check_prove - function_slug: fragmented - device: iPhone 14-16 - device_slug: iphone-14 - - function: bench_mobile::bench_passport_fragmented_age_check_prove - function_slug: fragmented - device: iPhone 16 Pro Max-18 - device_slug: iphone-16-pro-max - - function: bench_mobile::bench_oprf_prove - function_slug: oprf - device: iPhone SE 2022-15 - device_slug: iphone-se-2022 - - function: bench_mobile::bench_oprf_prove - function_slug: oprf - device: iPhone 14-16 - device_slug: iphone-14 - - function: bench_mobile::bench_oprf_prove - function_slug: oprf - device: iPhone 16 Pro Max-18 - device_slug: iphone-16-pro-max - ios_batch: [1, 2, 3, 4, 5] env: BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} @@ -332,9 +290,7 @@ jobs: working-directory: caller shell: bash env: - FUNCTIONS: ${{ matrix.ios_shard.function }} - IOS_MATRIX_DEVICE: ${{ matrix.ios_shard.device }} - IOS_BATCH_INDEX: ${{ matrix.ios_batch }} + FUNCTIONS: ${{ inputs.functions_ios != '' && inputs.functions_ios || inputs.functions }} ITERATIONS: ${{ inputs.iterations }} WARMUP: ${{ inputs.warmup }} RELEASE_FLAG: ${{ inputs.build_release && '--release' || '' }} @@ -366,7 +322,7 @@ jobs: fi echo "Running iOS benchmarks with profile ${MOBENCH_DEVICE_PROFILE}" - echo "iOS devices: ${IOS_MATRIX_DEVICE}" + 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 @@ -409,7 +365,7 @@ jobs: } ios_devices=() - IFS=',' read -r -a raw_ios_devices <<<"${IOS_MATRIX_DEVICE}" + IFS=',' read -r -a raw_ios_devices <<<"${IOS_DEVICE_SPECS}" for ios_device in "${raw_ios_devices[@]}"; do ios_device="$(echo "$ios_device" | xargs)" if [[ -n "$ios_device" ]]; then @@ -498,36 +454,36 @@ jobs: done } - batch_index="${IOS_BATCH_INDEX}" - sample_start=$(((batch_index - 1) * ios_split_batch_size + 1)) - if [ "${sample_start}" -gt "${function_iterations}" ]; then - echo "Skipping iOS split batch ${batch_index}; ${function_iterations} iteration(s) require fewer batches." - continue - fi - - remaining=$((function_iterations - sample_start + 1)) - batch_iterations="${ios_split_batch_size}" - if [ "${remaining}" -lt "${batch_iterations}" ]; then - batch_iterations="${remaining}" - fi - batch_warmup=0 - if [ "${batch_index}" -eq 1 ]; then - batch_warmup="${function_warmup}" - fi + batch_count=$(((function_iterations + ios_split_batch_size - 1) / ios_split_batch_size)) + for ((batch_index = 1; batch_index <= batch_count; batch_index++)); do + sample_start=$(((batch_index - 1) * ios_split_batch_size + 1)) + remaining=$((function_iterations - sample_start + 1)) + batch_iterations="${ios_split_batch_size}" + if [ "${remaining}" -lt "${batch_iterations}" ]; then + batch_iterations="${remaining}" + fi + batch_warmup=0 + if [ "${batch_index}" -eq 1 ]; then + batch_warmup="${function_warmup}" + fi - if ! run_ios_split_invocation "sample" "${batch_index}" "${batch_iterations}" "${batch_warmup}" "${split_output_dir}/sample-${batch_index}" "target/browserstack/ios/${function_slug}/${device_slug}/sample-${batch_index}"; then + if ! run_ios_split_invocation "sample" "${batch_index}" "${batch_iterations}" "${batch_warmup}" "${split_output_dir}/sample-${batch_index}" "target/browserstack/ios/${function_slug}/${device_slug}/sample-${batch_index}"; then split_failed="true" - fi + break + fi + done if [[ "${split_failed}" == "true" ]]; then exit 1 fi - continue - fi + python3 .github/scripts/merge_mobench_split_runs.py \ + --samples-dir "${split_output_dir}" \ + --output-dir "${result_output_dir}" \ + --iterations "${function_iterations}" \ + --warmup "${function_warmup}" \ + --target ios - if [ "${IOS_BATCH_INDEX}" -ne 1 ]; then - echo "Skipping non-split iOS batch ${IOS_BATCH_INDEX}; non-100-run jobs only use batch 1." continue fi @@ -590,7 +546,7 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: mobench-results-ios-${{ matrix.ios_shard.function_slug }}-${{ matrix.ios_shard.device_slug }}-batch-${{ matrix.ios_batch }} + name: mobench-results-ios path: | caller/target/mobench/ci/ios/** caller/target/browserstack/ios/** @@ -600,53 +556,12 @@ jobs: if-no-files-found: warn android: - name: Android BrowserStack benchmark (${{ matrix.android_shard.function_slug }} / ${{ matrix.android_shard.device_slug }}) + name: Android BrowserStack benchmark needs: ios if: always() && (inputs.platform == 'android' || inputs.platform == 'both') runs-on: macos-14 - timeout-minutes: 100 + timeout-minutes: 360 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 }} @@ -845,8 +760,7 @@ jobs: working-directory: caller shell: bash env: - FUNCTIONS: ${{ matrix.android_shard.function }} - ANDROID_DEVICE_SPEC: ${{ matrix.android_shard.device }} + FUNCTIONS: ${{ inputs.functions_android != '' && inputs.functions_android || inputs.functions }} ITERATIONS: ${{ inputs.iterations }} WARMUP: ${{ inputs.warmup }} RELEASE_FLAG: ${{ inputs.build_release && '--release' || '' }} @@ -878,9 +792,6 @@ jobs: 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 @@ -1266,7 +1177,7 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: mobench-results-android-${{ matrix.android_shard.function_slug }}-${{ matrix.android_shard.device_slug }} + name: mobench-results-android path: | caller/target/mobench/ci/android/** caller/target/browserstack/android/** @@ -1318,18 +1229,16 @@ jobs: continue-on-error: true uses: actions/download-artifact@v4 with: - pattern: mobench-results-ios-* + name: mobench-results-ios path: results/ios - merge-multiple: true - name: Download Android results if: always() continue-on-error: true uses: actions/download-artifact@v4 with: - pattern: mobench-results-android-* + name: mobench-results-android path: results/android - merge-multiple: true - name: Merge split iOS shard results if: always() From 474469fd87377fefb7640591e62e85489d2b22fd Mon Sep 17 00:00:00 2001 From: dcbuild3r Date: Thu, 11 Jun 2026 16:43:54 -0400 Subject: [PATCH 4/4] Fix mobile bench PR trigger [mobench:both] --- .github/workflows/mobile-bench-pr-auto.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mobile-bench-pr-auto.yml b/.github/workflows/mobile-bench-pr-auto.yml index 74dc11130..c73f24695 100644 --- a/.github/workflows/mobile-bench-pr-auto.yml +++ b/.github/workflows/mobile-bench-pr-auto.yml @@ -3,7 +3,7 @@ name: Mobile Bench PR Auto on: push: branches: - - dcbuild3r/main-mobench-fixtures + - dcbuilder/pr429-main-mobench-fixtures pull_request: types: [labeled] workflow_run: