Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
194 changes: 194 additions & 0 deletions .github/workflows/new-arch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
name: New Architecture (ffi / provers / tests)

# Runs on the groundwork branch and on PRs targeting main that touch new-arch paths.
# Intentionally separate from build-and-test.yml to avoid interfering with
# the existing CLI / template / platform build pipeline.

on:
push:
branches: [groundwork]
paths:
- "core/**"
- "provers/**"
- "ffi/**"
- "tests/**"
- "scripts/check-boundaries.sh"
- "Cargo.toml"
- "Cargo.lock"
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches: [main]
paths:
- "core/**"
- "provers/**"
- "ffi/**"
- "tests/**"
- "scripts/check-boundaries.sh"
- "Cargo.toml"
- "Cargo.lock"

env:
CARGO_TERM_COLOR: always

jobs:
# ------------------------------------------------------------------
# 1. cargo check — all new crates, including the tests build.rs
# ------------------------------------------------------------------
check:
name: cargo check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Rust
uses: ./.github/actions/setup-rust
with:
cache-key: new-arch-check

- name: core
run: cargo check -p mopro-core

- name: provers
run: cargo check -p circom-prover-adapter -p halo2-prover-adapter -p noir-prover-adapter

- name: ffi
run: cargo check -p mopro-build-common -p mopro-uniffi-backend -p mopro-wasm-backend -p mopro-flutter-backend -p mopro-react-native-backend

- name: test-app (the library compiled for mobile/wasm; must not appear in ffi/* deps)
run: cargo check -p test-app

- name: tests (verifies build.rs compiles; no arch env vars set so no external tools run)
run: cargo check -p tests

# ------------------------------------------------------------------
# 2. cargo test — unit tests (pure Rust, no toolchain deps)
# ------------------------------------------------------------------
test:
name: cargo test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Rust
uses: ./.github/actions/setup-rust
with:
cache-key: new-arch-test

- name: Run tests
run: |
cargo test \
-p mopro-core \
-p mopro-build-common \
-p circom-prover-adapter \
-p halo2-prover-adapter \
-p noir-prover-adapter \
-p mopro-uniffi-backend \
-p mopro-wasm-backend \
-p mopro-flutter-backend \
-p mopro-react-native-backend

# ------------------------------------------------------------------
# 3. Dependency boundary — ffi/* must not pull in core or provers
# ------------------------------------------------------------------
boundary:
name: dependency boundary check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Rust
uses: ./.github/actions/setup-rust
with:
cache-key: new-arch-boundary

- name: check ffi/* has no deps on core or provers
run: bash scripts/check-boundaries.sh

# ------------------------------------------------------------------
# 4. iOS bindings — build.rs triggers ios::build() via IOS_ARCHS
# Output: tests/MoproiOSBindings/MoproBindings.xcframework
# ------------------------------------------------------------------
binding_ios:
name: iOS bindings (uniffi → xcframework + Swift)
runs-on: macos-latest
steps:
- uses: actions/checkout@v4

- name: Setup Rust
uses: ./.github/actions/setup-rust
with:
cache-key: new-arch-binding-ios

- name: Add iOS simulator target
run: rustup target add aarch64-apple-ios-sim

- name: Build bindings
working-directory: tests
run: IOS_ARCHS=aarch64-apple-ios-sim cargo build -p tests

- name: Assert outputs exist
working-directory: test-app
run: |
set -e
test -d MoproiOSBindings/MoproBindings.xcframework \
|| { echo "::error::test-app/MoproiOSBindings/MoproBindings.xcframework not found"; exit 1; }
test -f MoproiOSBindings/mopro.swift \
|| { echo "::error::test-app/MoproiOSBindings/mopro.swift not found"; exit 1; }
echo "iOS bindings OK"
find MoproiOSBindings -type f | sort

- name: Upload bindings (on failure)
if: failure()
uses: actions/upload-artifact@v4
with:
name: ios-bindings-debug
path: test-app/MoproiOSBindings
if-no-files-found: ignore

# ------------------------------------------------------------------
# 5. Wasm bindings — build.rs triggers wasm::build() via WEB_ARCHS
# Output: tests/MoproWasmBindings/mopro_wasm_lib_bg.wasm
# ------------------------------------------------------------------
binding_wasm:
name: Wasm bindings (wasm-pack → pkg)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Rust (nightly-2025-02-20 for wasm-pack)
uses: ./.github/actions/setup-rust
with:
toolchain: nightly-2025-02-20
cache-key: new-arch-binding-wasm

- name: Add rust-src component (required by wasm-pack atomics build)
run: rustup component add rust-src --toolchain nightly-2025-02-20

- name: Add wasm32 target
run: rustup target add wasm32-unknown-unknown --toolchain nightly-2025-02-20

- name: Install wasm-pack
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh

- name: Build bindings
working-directory: tests
run: WEB_ARCHS=wasm32-unknown-unknown cargo build -p tests

- name: Assert outputs exist
working-directory: test-app
run: |
set -e
test -d MoproWasmBindings \
|| { echo "::error::test-app/MoproWasmBindings/ not found"; exit 1; }
test -f MoproWasmBindings/mopro_wasm_lib_bg.wasm \
|| { echo "::error::test-app/MoproWasmBindings/mopro_wasm_lib_bg.wasm not found"; exit 1; }
echo "Wasm bindings OK"
find MoproWasmBindings -type f | sort

- name: Upload bindings (on failure)
if: failure()
uses: actions/upload-artifact@v4
with:
name: wasm-bindings-debug
path: test-app/MoproWasmBindings
if-no-files-found: ignore
146 changes: 146 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# CLAUDE.md — mopro (root)

This file defines architectural invariants for the mopro monorepo during and
after the `core` / `provers` / `ffi` / `cli` restructuring. It is the source
of truth for dependency boundaries that the compiler will NOT enforce for
you — read this before touching any crate under `provers/`, `ffi/`, or `cli/`.

## Target structure

```
mopro/
├── core/ # Prover trait + shared types. No FFI deps, no internal deps.
├── provers/ # "adapters" — thin wrappers that implement core::Prover
│ ├── circom-adapter/ # delegates to external circom-prover crate
│ ├── halo2-adapter/ # wraps user-supplied circuit fn pointers
│ ├── noir-adapter/ # delegates to noir-rs (barretenberg feature)
│ └── plonk-prover/ # future community slot
├── ffi/
│ ├── common/ # Arch/Mode/PlatformBuilder traits + shared build utilities
│ ├── uniffi/ # build pipeline: runs uniffi-bindgen, emits Swift/Kotlin
│ ├── flutter/ # build pipeline: runs FRB codegen, emits Dart package
│ ├── react-native/ # build pipeline: runs uniffi-bindgen-react-native
│ └── wasm/ # build pipeline: runs wasm-pack, emits pkg/
├── cli/ # mopro-cli — the ONLY composition root
├── templates/ # scaffold templates per (adapter × platform)
├── tests/
├── test-vectors/
└── docs/
```

## Dependency direction

```
core/ ──► (nothing internal)
provers/* ──► core + external prover crate
+ optional FFI runtime/annotation crates
(uniffi, wasm-bindgen, flutter_rust_bridge, serde)
ffi/* ──► (nothing internal) — build pipeline only
cli/ ──► core, provers/*, ffi/*
```

### `core/` — defines the contract, nothing else

- Holds the `Prover` trait, `MoproError`, and shared types (`ProofBytes`, etc.).
- Zero FFI-tool dependencies: no `uniffi`, no `wasm-bindgen`, no `flutter_rust_bridge`.
- Does not know that `ffi/` or `cli/` exist.

### `provers/*` — implement the contract AND own FFI compatibility

Each adapter crate has **two responsibilities**:

1. **Implement `core::Prover`** — a thin Rust wrapper that delegates all actual
proving/verifying logic to an external crate (circom-prover, noir-rs, etc.).
No proving code lives inside this monorepo.

2. **Own FFI compatibility for their output types** — the `Output` type (and any
supporting structs/enums) carries the annotations that make it directly usable
by all three FFI layers via optional Cargo features:

| Feature flag | FFI layer | What gets added |
|---------------|------------------------|----------------------------------------------------------------------|
| `uniffi` | iOS / Android (UniFFI) | `uniffi::Record` / `uniffi::Enum` derives; `setup_scaffolding!()` in `lib.rs` |
| `wasm` | Browser (wasm-bindgen) | `#[wasm_bindgen]` on wrapper functions; `serde` for `JsValue` conversion |
| `flutter` | Flutter (FRB) | FRB codegen scans public types; `String`/`Vec<u8>`/`Vec<String>` are native — no extra annotation needed |

`serde` (`Serialize` / `Deserialize`) is always present on output types regardless
of feature flags, because JSON serialisation is useful in every context.

**Allowed optional dependencies in `provers/*`:**
- `uniffi` — runtime proc-macros (`Record`, `Enum`, `setup_scaffolding!`)
- `wasm-bindgen` — proc-macros + JS glue for browser exports
- `flutter_rust_bridge` — proc-macros for FRB codegen
- `serde` — always on
- The external prover crate being adapted

**Never allowed in `provers/*`:**
- `uniffi-bindgen` — this is the Swift/Kotlin *code generator* (CLI tool), not
the runtime. It belongs in the `ffi/uniffi` build pipeline.
- Any `ffi/*` crate as a hard dependency.

3. **Adding a new adapter must require zero changes to `ffi/*`.**
If it does require changes, the boundary has been violated — stop and re-check.

### `ffi/*` — build pipeline only, no ZK knowledge, no Rust types

`ffi/*` crates are **build-pipeline orchestrators**, not Rust glue-code crates:

- `ffi/uniffi` — invokes `uniffi-bindgen` to generate Swift/Kotlin from the adapter's scaffolding; runs `cargo build` with `--features uniffi`.
- `ffi/wasm` — runs `wasm-pack build` with `--features wasm`; sets up `pkg/` output.
- `ffi/flutter` — runs FRB codegen; sets up the Dart package.
- `ffi/react-native` — runs `uniffi-bindgen-react-native`; sets up the JS package.

Rules:
- No `ffi/*` crate's `Cargo.toml` lists `mopro-core` or any `provers/*` crate as a dependency.
- No Rust types related to proving live in `ffi/*` — those live in the adapter.
- If you find yourself writing "what a proof is" inside `ffi/*`, that logic belongs in the adapter or in `cli/`.

### `cli/` — the only composition root

- The only crate allowed to depend on both a specific adapter (from `provers/*`)
and a specific FFI backend (from `ffi/*`) simultaneously.
- Given a user's choice of (adapter × platform), `cli/` enables the right
feature flags on the adapter and invokes the right `ffi/*` pipeline.
- All "which adapters exist" and "which backend to use" logic lives here.

## Verifying the boundaries

Run before finishing any change under `ffi/`, `provers/`, or `core/`.

```bash
# ffi/backends must NOT pull in core or any adapter (the one invariant the
# compiler won't catch for you)
cargo tree -p <ffi-backend-crate-name> | grep -iE "mopro-core|circom-prover-adapter|halo2-prover-adapter|noir-prover-adapter"
# Should print nothing.
```

## Migration notes

- The old `mopro-ffi` crate combined (1) FFI tooling orchestration and (2) wiring
specific provers into specific platforms. This refactor splits them:
(1) → `ffi/*`, (2) → `cli/`.
- Do not recreate a crate that re-merges these two responsibilities.
- `circom-prover` already existed as a standalone crate on crates.io and is
adapted (not rewritten) under `provers/circom-adapter`.
- `halo2-adapter` and `noir-adapter` extract logic that used to live inside
`mopro-ffi`.
- `plonk-prover` does not exist yet — it is a placeholder proving that the
architecture admits community-contributed adapters with zero changes to
`ffi/` or `cli/`.

## Status

> Update this section as migration proceeds.
> States: `not started` / `in progress` / `done`

- [x] `core/` — `Prover` trait, `MoproError`, `ProofBytes` defined; no FFI deps
- [x] `provers/circom-adapter` — `Prover` impl done; `serde` + optional `uniffi` derives on output types; compiles (integration tests need zkey test-vectors)
- [x] `provers/halo2-adapter` — `Prover` impl done; `Halo2Output` with `serde` + optional `uniffi::Record`
- [x] `provers/noir-adapter` — `Prover` impl done; Input/Output are FFI-primitive (`Vec<String>` / `Vec<u8>`); `barretenberg` feature gates noir-rs
- [x] `ffi/uniffi` — `IosPlatform`/`AndroidPlatform` defined locally; `impl PlatformBuilder` for both; compiles; no deps on core or provers
- [x] `ffi/flutter` — `FlutterPlatform` defined locally; `impl PlatformBuilder`; compiles; no deps on core or provers
- [x] `ffi/react-native` — `ReactNativePlatform` defined locally; `impl PlatformBuilder`; compiles; no deps on core or provers
- [x] `ffi/wasm` — `WebPlatform` defined locally; `impl PlatformBuilder`; compiles; no deps on core or provers
- [ ] `cli/` — updated to composition root (not started)
- [ ] old `mopro-ffi` crate removed (not started)
- [ ] `templates/` updated to reflect new (adapter × platform) structure (not started)
Loading
Loading