Skip to content

ragu_core: add Gadget::from_wires_gadget on GadgetKind#8

Open
mitschabaude wants to merge 12 commits into
zksec/clean-integrationfrom
zksec/fv-ci-ux
Open

ragu_core: add Gadget::from_wires_gadget on GadgetKind#8
mitschabaude wants to merge 12 commits into
zksec/clean-integrationfrom
zksec/fv-ci-ux

Conversation

@mitschabaude

@mitschabaude mitschabaude commented Apr 20, 2026

Copy link
Copy Markdown
Member

Builds on top of the clean-integration PR. Three additions.

Gadget::from_wires in ragu_core

New kind-level method GadgetKind::from_wires_gadget that constructs any gadget by pulling wires from an iterator in the canonical map_gadget order. Witness payloads are synthesized via MaybeKind::empty, so it only compiles against drivers whose MaybeKind represents non-existing values — real drivers hit a const panic at monomorphization. Gadget::from_wires is a defaulted proxy on top, mirroring the existing map/map_gadget split.

The #[derive(Gadget)] macro generates it automatically. Hand-impls updated for (), [G; N], (G1, G2), Box<G>, Demoted, FixedVec, InternalCircuitValues, RxValues.

On the extraction side, this replaces the Point::constant(dr, generator()) + WireDeserializer template trick with a clean dr.alloc_input::<T>().

Gadget::to_wires + roundtrip tests

Dual of from_wires: a defaulted method that serializes a gadget into a flat Vec<D::Wire> via map. Retires the extraction-local WireCollector::collect_from helper.

The round-trip invariant (from_wires and to_wires are mutual inverses for MaybeKind = Empty drivers) is documented on both methods and covered by three test modules:

  • crates/ragu_core/src/gadgets/roundtrip.rs — foreign impls ((), arrays, tuples, Box) plus hand-written Single/Pair fixtures.
  • crates/ragu_primitives/tests/roundtrip.rsElement (derive), FixedVec, Demoted.
  • crates/ragu_pcd/src/internal/native/stages/query.rsInternalCircuitValues, RxValues.

extract_gadget! macro

New tiny proc-macro crate lean_extraction_macros exporting extract_gadget!(Name, Field, closure). The closure's typed params become input gadgets (via alloc_input); the last untyped param is the driver; the body's return value is flattened via Gadget::to_wires (tuple outputs compose through the foreign (G1, G2) impl).

Every instance collapses to a single line, and the five instance files are consolidated into a single instances.rs:

extract_gadget!(PointDoubleInstance, Fp, |p: Point<_, EpAffine>, dr| p.double(dr));

Extracted Lean output is byte-for-byte unchanged throughout.

Test plan

  • cargo build --locked --workspace
  • cargo test --locked --workspace
  • cargo run --locked -p lean_extraction -- check — every generated Lean file byte-identical
  • cargo run --locked -p lean_extraction -- export — succeeds
  • lake build --wfail under qa/lean/ — Lean project builds clean

🤖 Generated with Claude Code

mitschabaude and others added 7 commits April 20, 2026 09:41
New kind-level method that constructs a gadget by pulling wires from an
iterator in the same canonical order that map_gadget visits them. Witness
payloads are synthesized via MaybeKind::empty, so the method only
compiles against drivers whose MaybeKind represents non-existing values
(e.g. extraction drivers). Real drivers hit a const panic at
monomorphization time, preventing silent stub-witness bugs.

Gadget gets a defaulted from_wires proxy that forwards to
Kind::from_wires_gadget, mirroring how map/map_gadget and
enforce_equal/enforce_equal_gadget are split today.

Derive generates from_wires_gadget for every #[derive(Gadget)] user:

  - #[ragu(wire)]: pull one item from the iterator.
  - #[ragu(value)]: synthesize via MaybeKind::empty().
  - #[ragu(gadget)]: recurse via the Gadget::from_wires proxy.
  - #[ragu(phantom)]: PhantomData.

Hand-impls updated for (), [G; N], (G1, G2), Box<G>, Demoted,
FixedVec, and the TwoWires/OneWire test fixtures.

On the extraction side, ExtractionDriver::alloc_input<T> now takes any
T: Gadget and constructs it from a generator iterator, replacing the
WireDeserializer template trick. WireDeserializer is deleted; every
instance file collapses from ~25 lines of setup to a single
`dr.alloc_input()?` call plus the gadget method. Extracted Lean output
is byte-for-byte unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New tiny proc-macro crate `lean_extraction_macros` exporting a single
`extract_gadget!(Name, Field, closure)` macro. The closure's typed
parameters become input gadgets (allocated via `dr.alloc_input()`); the
last (untyped) parameter is the driver; the body returns `Result<T>`
where `T` is anything collectable by `WireCollector`, including tuples
of gadgets (which go through the foreign `(G1, G2)` Gadget impl).

Collapses each instance file to a single invocation:

- point_double.rs: 26 → 10 lines
- point_negate.rs: 26 → 10 lines
- point_add_incomplete.rs: 36 → 13 lines
- point_alloc.rs: 44 → 20 lines (two invocations, Fp + Fq)

Extracted Lean output is byte-for-byte unchanged.

The macro has to live in its own crate (rustc requires `proc-macro =
true` to be an exclusive property of a crate). We don't fold it into
`ragu_macros` because that crate is about ragu_core derive macros, and
this macro references `lean_extraction` internals.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…sec/fv-ci-ux

# Conflicts:
#	fv/crates/lean_extraction/src/instances/point_add_incomplete.rs
#	fv/crates/lean_extraction/src/instances/point_alloc.rs
#	fv/crates/lean_extraction/src/instances/point_double.rs
#	fv/crates/lean_extraction/src/instances/point_negate.rs
@TalDerei

TalDerei commented Apr 20, 2026

Copy link
Copy Markdown

constructing a gadget from a raw flat wire stream feels like it has footguns, though that’s just a vague and unfounded intuition. cc @ebfull

mitschabaude and others added 4 commits April 20, 2026 16:59
With each instance now a single `extract_gadget!` invocation, the four
per-instance files (point_double, point_negate, point_alloc,
point_add_incomplete) plus their `mod.rs` are excessive. Merge them into
a single `instances.rs` module with shared `use` statements at the top.

main.rs imports simplify from per-submodule paths to a single flat
`use crate::instances::{...};`.

Extracted Lean output is byte-for-byte unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all extracted instances now live here

- Apply nightly rustfmt to the consolidated `instances.rs`.
- Drop the redundant explicit path on `[`ExtractionDriver`]` — the type
  is already in scope via `use`, and nightly rustdoc rejects the repeated
  target under `-D warnings`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mitschabaude mitschabaude marked this pull request as ready for review April 20, 2026 15:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants