Skip to content
Open
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
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ members = [
"crates/ragu_pcd",
"crates/ragu_testing",
"qa/crates/lean_extraction",
"qa/crates/lean_extraction_macros",
]

resolver = "2"
Expand Down
31 changes: 31 additions & 0 deletions crates/ragu_core/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,24 @@ mod tests {
dr.enforce_equal(&a.b, &b.b)?;
Ok(())
}

fn from_wires_gadget<'dr, D: Driver<'dr, F = FieldType>, I: Iterator<Item = D::Wire>>(
iter: &mut I,
) -> Result<Bound<'dr, D, Self>> {
let a = iter.next().ok_or(crate::Error::VectorLengthMismatch {
expected: 2,
actual: 0,
})?;
let b = iter.next().ok_or(crate::Error::VectorLengthMismatch {
expected: 2,
actual: 1,
})?;
Ok(TwoWires {
a,
b,
_marker: core::marker::PhantomData,
})
}
}

impl<'dr, D: Driver<'dr>> Gadget<'dr, D> for TwoWires<'dr, D> {
Expand Down Expand Up @@ -272,6 +290,19 @@ mod tests {
dr.enforce_equal(&a.w, &b.w)?;
Ok(())
}

fn from_wires_gadget<'dr, D: Driver<'dr, F = FieldType>, I: Iterator<Item = D::Wire>>(
iter: &mut I,
) -> Result<Bound<'dr, D, Self>> {
let w = iter.next().ok_or(crate::Error::VectorLengthMismatch {
expected: 1,
actual: 0,
})?;
Ok(OneWire {
w,
_marker: core::marker::PhantomData,
})
}
}

impl<'dr, D: Driver<'dr>> Gadget<'dr, D> for OneWire<'dr, D> {
Expand Down
18 changes: 18 additions & 0 deletions crates/ragu_core/src/drivers/emulator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,24 @@ mod tests {
dr.enforce_equal(&a.b, &b.b)?;
Ok(())
}

fn from_wires_gadget<'dr, D: Driver<'dr, F = FieldType>, I: Iterator<Item = D::Wire>>(
iter: &mut I,
) -> Result<Bound<'dr, D, Self>> {
let a = iter.next().ok_or(crate::Error::VectorLengthMismatch {
expected: 2,
actual: 0,
})?;
let b = iter.next().ok_or(crate::Error::VectorLengthMismatch {
expected: 2,
actual: 1,
})?;
Ok(TwoWires {
a,
b,
_marker: core::marker::PhantomData,
})
}
}

impl<'dr, D: Driver<'dr>> crate::gadgets::Gadget<'dr, D> for TwoWires<'dr, D> {
Expand Down
86 changes: 86 additions & 0 deletions crates/ragu_core/src/gadgets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@

mod foreign;

#[cfg(test)]
mod roundtrip;

use ff::Field;

use super::{
Expand Down Expand Up @@ -138,6 +141,32 @@ pub trait Gadget<'dr, D: Driver<'dr>>: Clone {
Self::Kind::enforce_equal_gadget::<D2, D>(dr, self, other)
}

/// Proxy for [`GadgetKind::from_wires_gadget`].
///
/// # Round-trip invariant
///
/// `from_wires` and [`to_wires`](Self::to_wires) are mutual inverses
/// whenever `D::MaybeKind` represents non-existing values (the only
/// drivers for which `from_wires` compiles). Specifically, for any wire
/// slice `ws` of the correct length:
///
/// ```text
/// Self::from_wires(&mut ws.iter().cloned())?.to_wires()? == ws
/// ```
///
/// and for any gadget `g`:
///
/// ```text
/// Self::from_wires(&mut g.to_wires()?.into_iter())? ≡ g
/// ```
///
/// (The second equation is exact because `D::MaybeKind = Empty` makes
/// all witness values zero-sized, so `from_wires` reconstructs the full
/// state, not just the wires.)
fn from_wires<I: Iterator<Item = D::Wire>>(iter: &mut I) -> Result<Self> {
Self::Kind::from_wires_gadget::<D, I>(iter)
}

/// Returns how many wires are in this gadget.
///
/// Gadgets do not vary in the number of wires they contain, so this
Expand Down Expand Up @@ -170,6 +199,40 @@ pub trait Gadget<'dr, D: Driver<'dr>>: Clone {
self.map(&mut counter)?;
Ok(counter.count)
}

/// Serializes this gadget into a flat `Vec` of wires in the same canonical
/// order that [`map`](Self::map) visits them.
///
/// Mutual inverse of [`from_wires`](Self::from_wires) for drivers with
/// `MaybeKind = Empty`; see that method for the full round-trip
/// invariant.
///
/// # Errors
///
/// Returns an error if the underlying [`GadgetKind::map_gadget`] fails.
fn to_wires(&self) -> Result<alloc::vec::Vec<D::Wire>> {
struct WireCollector<Src: DriverTypes> {
wires: alloc::vec::Vec<Src::ImplWire>,
_marker: core::marker::PhantomData<Src>,
}

impl<F: Field, Src: DriverTypes<ImplField = F>> WireMap<F> for WireCollector<Src> {
type Src = Src;
type Dst = core::marker::PhantomData<F>;

fn convert_wire(&mut self, wire: &Src::ImplWire) -> Result<()> {
self.wires.push(wire.clone());
Ok(())
}
}

let mut collector = WireCollector::<D> {
wires: alloc::vec::Vec::new(),
_marker: core::marker::PhantomData,
};
self.map(&mut collector)?;
Ok(collector.wires)
}
}

/// A driver-agnostic kindness of a gadget.
Expand Down Expand Up @@ -240,6 +303,29 @@ pub unsafe trait GadgetKind<F: Field>: core::any::Any {
a: &Bound<'dr, D2, Self>,
b: &Bound<'dr, D2, Self>,
) -> Result<()>;

/// Constructs a gadget of this kind by pulling wires from `iter` in the
/// same canonical order that [`map_gadget`](Self::map_gadget) visits them.
///
/// Witness payloads are synthesized via
/// [`MaybeKind::empty`](crate::maybe::MaybeKind::empty), so this method is
/// only usable with drivers whose `MaybeKind` represents non-existing
/// values (e.g. extraction drivers). Monomorphization will fail via a
/// `const panic` for drivers that track real witnesses.
///
/// # Round-trip invariant
///
/// Mutual inverse of serialization via [`Gadget::to_wires`] (which walks
/// the same canonical order through [`map_gadget`](Self::map_gadget)). See
/// [`Gadget::from_wires`] for the full statement of the invariant.
///
/// # Errors
///
/// Returns [`Error::VectorLengthMismatch`](crate::Error::VectorLengthMismatch)
/// if `iter` is exhausted before the gadget's wires have been consumed.
fn from_wires_gadget<'dr, D: Driver<'dr, F = F>, I: Iterator<Item = D::Wire>>(
iter: &mut I,
) -> Result<Bound<'dr, D, Self>>;
}

/// Automatically derives the [`Gadget`], [`GadgetKind`] and [`Clone`] traits
Expand Down
32 changes: 32 additions & 0 deletions crates/ragu_core/src/gadgets/foreign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ mod unit_impl {
) -> Result<()> {
Ok(())
}

fn from_wires_gadget<'dr, D: Driver<'dr, F = F>, I: Iterator<Item = D::Wire>>(
_iter: &mut I,
) -> Result<Bound<'dr, D, Self>> {
Ok(())
}
}
}

Expand Down Expand Up @@ -96,6 +102,18 @@ mod array_impl {
}
Ok(())
}

fn from_wires_gadget<'dr, D: Driver<'dr, F = F>, I: Iterator<Item = D::Wire>>(
iter: &mut I,
) -> Result<Bound<'dr, D, Self>> {
let mut result = Vec::with_capacity(N);
for _ in 0..N {
result.push(G::from_wires_gadget::<D, I>(iter)?);
}
Ok(result
.try_into()
.unwrap_or_else(|_| unreachable!("Vec had exactly N elements")))
}
}
}

Expand Down Expand Up @@ -139,6 +157,14 @@ mod pair_impl {
G2::enforce_equal_gadget(dr, &a.1, &b.1)?;
Ok(())
}

fn from_wires_gadget<'dr, D: Driver<'dr, F = F>, I: Iterator<Item = D::Wire>>(
iter: &mut I,
) -> Result<Bound<'dr, D, Self>> {
let a = G1::from_wires_gadget::<D, I>(iter)?;
let b = G2::from_wires_gadget::<D, I>(iter)?;
Ok((a, b))
}
}
}

Expand Down Expand Up @@ -178,5 +204,11 @@ mod box_impl {
) -> Result<()> {
G::enforce_equal_gadget(dr, a, b)
}

fn from_wires_gadget<'dr, D: Driver<'dr, F = F>, I: Iterator<Item = D::Wire>>(
iter: &mut I,
) -> Result<Bound<'dr, D, Self>> {
Ok(Box::new(G::from_wires_gadget::<D, I>(iter)?))
}
}
}
Loading
Loading