#[cfg(feature = "multithreaded")] use rayon::prelude::*;
use std::any::Any; use std::convert::TryFrom; use std::fmt::Debug; use std::marker::PhantomData;
/// For input polynomials larger than or equal to this threshold, gadgets will use FFT for /// polynomial multiplication. Otherwise, the gadget uses direct multiplication. const FFT_THRESHOLD: usize = 60;
/// An arity-2 gadget that multiples its inputs. #[derive(Clone, Debug, Eq, PartialEq)] pubstruct Mul<F: FftFriendlyFieldElement> { /// Size of buffer for FFT operations.
n: usize, /// Inverse of `n` in `F`.
n_inv: F, /// The number of times this gadget will be called.
num_calls: usize,
}
impl<F: FftFriendlyFieldElement> Mul<F> { /// Return a new multiplier gadget. `num_calls` is the number of times this gadget will be /// called by the validity circuit. pubfn new(num_calls: usize) -> Self { let n = gadget_poly_fft_mem_len(2, num_calls); let n_inv = F::from(F::Integer::try_from(n).unwrap()).inv(); Self {
n,
n_inv,
num_calls,
}
}
/// An arity-1 gadget that evaluates its input on some polynomial. // // TODO Make `poly` an array of length determined by a const generic. #[derive(Clone, Debug, Eq, PartialEq)] pubstruct PolyEval<F: FftFriendlyFieldElement> {
poly: Vec<F>, /// Size of buffer for FFT operations.
n: usize, /// Inverse of `n` in `F`.
n_inv: F, /// The number of times this gadget will be called.
num_calls: usize,
}
impl<F: FftFriendlyFieldElement> PolyEval<F> { /// Returns a gadget that evaluates its input on `poly`. `num_calls` is the number of times /// this gadget is called by the validity circuit. pubfn new(poly: Vec<F>, num_calls: usize) -> Self { let n = gadget_poly_fft_mem_len(poly_deg(&poly), num_calls); let n_inv = F::from(F::Integer::try_from(n).unwrap()).inv(); Self {
poly,
n,
n_inv,
num_calls,
}
}
}
/// Trait for abstracting over [`ParallelSum`]. pubtrait ParallelSumGadget<F: FftFriendlyFieldElement, G>: Gadget<F> + Debug { /// Wraps `inner` into a sum gadget that calls it `chunks` many times, and adds the reuslts. fn new(inner: G, chunks: usize) -> Self;
}
/// A wrapper gadget that applies the inner gadget to chunks of input and returns the sum of the /// outputs. The arity is equal to the arity of the inner gadget times the number of times it is /// called. #[derive(Clone, Debug, Eq, PartialEq)] pubstruct ParallelSum<F: FftFriendlyFieldElement, G: Gadget<F>> {
inner: G,
chunks: usize,
phantom: PhantomData<F>,
}
/// A wrapper gadget that applies the inner gadget to chunks of input and returns the sum of the /// outputs. The arity is equal to the arity of the inner gadget times the number of chunks. The sum /// evaluation is multithreaded. #[cfg(feature = "multithreaded")] #[cfg_attr(docsrs, doc(cfg(feature = "multithreaded")))] #[derive(Clone, Debug, Eq, PartialEq)] pubstruct ParallelSumMultithreaded<F: FftFriendlyFieldElement, G: Gadget<F>> {
serial_sum: ParallelSum<F, G>,
}
// Create a copy of the inner gadget and two working buffers on each thread. Evaluate the // gadget on each input polynomial, using the first temporary buffer as an output buffer. // Then accumulate that result into the second temporary buffer, which acts as a running // sum. Then, discard everything but the partial sums, add them, and finally copy the sum // to the output parameter. This is equivalent to the single threaded calculation in // ParallelSum, since we only rearrange additions, and field addition is associative. let res = inp
.par_chunks(self.serial_sum.inner.arity())
.fold(
|| ParallelSumFoldState::new(&self.serial_sum.inner, outp.len()),
|mut state, chunk| {
state
.inner
.call_poly(&mut state.partial_output, chunk)
.unwrap(); for (sum_elem, output_elem) in state
.partial_sum
.iter_mut()
.zip(state.partial_output.iter())
{
*sum_elem += *output_elem;
}
state
},
)
.map(|state| state.partial_sum)
.reduce(
|| vec![F::zero(); outp.len()],
|mut x, y| { for (xi, yi) in x.iter_mut().zip(y.iter()) {
*xi += *yi;
}
x
},
);
// Check that the input parameters of g.call() are well-formed. fn gadget_call_check<F: FftFriendlyFieldElement, G: Gadget<F>>(
gadget: &G,
in_len: usize,
) -> Result<(), FlpError> { if in_len != gadget.arity() { return Err(FlpError::Gadget(format!( "unexpected number of inputs: got {}; want {}",
in_len,
gadget.arity()
)));
}
if in_len == 0 { return Err(FlpError::Gadget("can't call an arity-0 gadget".to_string()));
}
Ok(())
}
// Check that the input parameters of g.call_poly() are well-formed. fn gadget_call_poly_check<F: FftFriendlyFieldElement, G: Gadget<F>>(
gadget: &G,
outp: &[F],
inp: &[Vec<F>],
) -> Result<(), FlpError> where
G: Gadget<F>,
{
gadget_call_check(gadget, inp.len())?;
for i in1..inp.len() { if inp[i].len() != inp[0].len() { return Err(FlpError::Gadget( "gadget called on wire polynomials with different lengths".to_string(),
));
}
}
#[cfg(feature = "multithreaded")] usecrate::field::FieldElement; usecrate::field::{random_vector, Field64 as TestField}; usecrate::prng::Prng;
#[test] fn test_mul() { // Test the gadget with input polynomials shorter than `FFT_THRESHOLD`. This exercises the // naive multiplication code path. let num_calls = FFT_THRESHOLD / 2; letmut g: Mul<TestField> = Mul::new(num_calls);
gadget_test(&mut g, num_calls);
// Test the gadget with input polynomials longer than `FFT_THRESHOLD`. This exercises // FFT-based polynomial multiplication. let num_calls = FFT_THRESHOLD; letmut g: Mul<TestField> = Mul::new(num_calls);
gadget_test(&mut g, num_calls);
}
#[test] fn test_poly_eval() { let poly: Vec<TestField> = random_vector(10).unwrap();
#[test] fn test_parallel_sum() { let num_calls = 10; let chunks = 23;
letmut g = ParallelSum::new(Mul::<TestField>::new(num_calls), chunks);
gadget_test(&mut g, num_calls);
}
#[test] #[cfg(feature = "multithreaded")] fn test_parallel_sum_multithreaded() { use std::iter;
for num_calls in [1, 10, 100] { let chunks = 23;
letmut g = ParallelSumMultithreaded::new(Mul::new(num_calls), chunks);
gadget_test(&mut g, num_calls);
// Test that the multithreaded version has the same output as the normal version. letmut g_serial = ParallelSum::new(Mul::new(num_calls), chunks);
assert_eq!(g.arity(), g_serial.arity());
assert_eq!(g.degree(), g_serial.degree());
assert_eq!(g.calls(), g_serial.calls());
let arity = g.arity(); let degree = g.degree();
// Test that both gadgets evaluate to the same value when run on scalar inputs. let inp: Vec<TestField> = random_vector(arity).unwrap(); let result = g.call(&inp).unwrap(); let result_serial = g_serial.call(&inp).unwrap();
assert_eq!(result, result_serial);
// Test that both gadgets evaluate to the same value when run on polynomial inputs. letmut poly_outp =
vec![TestField::zero(); (degree * num_calls + 1).next_power_of_two()]; letmut poly_outp_serial =
vec![TestField::zero(); (degree * num_calls + 1).next_power_of_two()]; letmut prng: Prng<TestField, _> = Prng::new().unwrap(); let poly_inp: Vec<_> = iter::repeat_with(|| {
iter::repeat_with(|| prng.get())
.take(1 + num_calls)
.collect::<Vec<_>>()
})
.take(arity)
.collect();
// Test that calling g.call_poly() and evaluating the output at a given point is equivalent // to evaluating each of the inputs at the same point and applying g.call() on the results. fn gadget_test<F: FftFriendlyFieldElement, G: Gadget<F>>(g: &mut G, num_calls: usize) { let wire_poly_len = (1 + num_calls).next_power_of_two(); letmut prng = Prng::new().unwrap(); letmut inp = vec![F::zero(); g.arity()]; letmut gadget_poly = vec![F::zero(); gadget_poly_fft_mem_len(g.degree(), num_calls)]; letmut wire_polys = vec![vec![F::zero(); wire_poly_len]; g.arity()];
let r = prng.get(); for i in0..g.arity() { for j in0..wire_poly_len {
wire_polys[i][j] = prng.get();
}
inp[i] = poly_eval(&wire_polys[i], r);
}
g.call_poly(&mut gadget_poly, &wire_polys).unwrap(); let got = poly_eval(&gadget_poly, r); let want = g.call(&inp).unwrap();
assert_eq!(got, want);
// Repeat the call to make sure that the gadget's memory is reset properly between calls.
g.call_poly(&mut gadget_poly, &wire_polys).unwrap(); let got = poly_eval(&gadget_poly, r);
assert_eq!(got, want);
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.12 Sekunden
(vorverarbeitet am 2026-06-17)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.