1use midnight_proofs::{circuit::Layouter, plonk::Error};
20use num_bigint::BigUint;
21use num_integer::Integer;
22use num_traits::{One, Zero};
23
24use crate::{
25 instructions::{ArithInstructions, RangeCheckInstructions},
26 types::InnerValue,
27 utils::util::big_to_fe,
28 CircuitField,
29};
30
31pub trait DivisionInstructions<F, Assigned>:
33 ArithInstructions<F, Assigned> + RangeCheckInstructions<F, Assigned>
34where
35 F: CircuitField,
36 Assigned: InnerValue,
37 Assigned::Element: CircuitField,
38{
39 fn div_rem(
68 &self,
69 layouter: &mut impl Layouter<F>,
70 dividend: &Assigned,
71 divisor: BigUint,
72 dividend_bound: Option<BigUint>,
73 ) -> Result<(Assigned, Assigned), Error> {
74 if divisor == BigUint::one() {
75 return Ok((
76 dividend.clone(),
77 self.assign_fixed(layouter, Assigned::Element::from(0))?,
78 ));
79 }
80
81 let dividend_bound = dividend_bound.unwrap_or((-Assigned::Element::from(1)).to_biguint());
82 assert!(divisor > BigUint::zero());
83 assert!(divisor <= dividend_bound);
84
85 let (q, r) = dividend
86 .value()
87 .map(|v| {
88 let (q, r) = v.to_biguint().div_rem(&divisor);
89 (big_to_fe(q), big_to_fe(r))
90 })
91 .unzip();
92
93 let q_strict_bound = (dividend_bound / &divisor) + BigUint::one();
94
95 let r = self.assign_lower_than_fixed(layouter, r, &divisor)?;
96 let q = self.assign_lower_than_fixed(layouter, q, &q_strict_bound)?;
97
98 let sum = self.linear_combination(
99 layouter,
100 &[
101 (big_to_fe(divisor), q.clone()),
102 (Assigned::Element::from(1), r.clone()),
103 ],
104 Assigned::Element::from(0),
105 )?;
106 self.assert_equal(layouter, dividend, &sum)?;
107
108 Ok((q, r))
109 }
110
111 fn rem(
139 &self,
140 layouter: &mut impl Layouter<F>,
141 input: &Assigned,
142 modulus: BigUint,
143 input_bound: Option<BigUint>,
144 ) -> Result<Assigned, Error> {
145 self.div_rem(layouter, input, modulus, input_bound).map(|(_, r)| r)
146 }
147}
148
149#[cfg(test)]
150pub(crate) mod tests {
151 use std::marker::PhantomData;
152
153 use ff::FromUniformBytes;
154 use midnight_proofs::{
155 circuit::{SimpleFloorPlanner, Value},
156 dev::MockProver,
157 plonk::{Circuit, ConstraintSystem},
158 };
159
160 use super::*;
161 use crate::{
162 testing_utils::FromScratch,
163 types::InnerValue,
164 utils::circuit_modeling::{circuit_to_json, cost_measure_end, cost_measure_start},
165 };
166
167 struct TestCircuit<F, Assigned, DivChip>
168 where
169 Assigned: InnerValue,
170 {
171 dividend: Value<Assigned::Element>,
172 divisor: BigUint,
173 expected: (Assigned::Element, Assigned::Element),
174 _marker: PhantomData<(F, DivChip)>,
175 }
176
177 impl<F, Assigned, DivChip> Circuit<F> for TestCircuit<F, Assigned, DivChip>
178 where
179 F: CircuitField,
180 Assigned: InnerValue,
181 Assigned::Element: CircuitField,
182 DivChip: DivisionInstructions<F, Assigned> + FromScratch<F>,
183 {
184 type Config = <DivChip as FromScratch<F>>::Config;
185
186 type FloorPlanner = SimpleFloorPlanner;
187
188 type Params = ();
189
190 fn without_witnesses(&self) -> Self {
191 unreachable!();
192 }
193
194 fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
195 let committed_instance_column = meta.instance_column();
196 let instance_column = meta.instance_column();
197 DivChip::configure_from_scratch(
198 meta,
199 &mut vec![],
200 &mut vec![],
201 &[committed_instance_column, instance_column],
202 )
203 }
204
205 fn synthesize(
206 &self,
207 config: Self::Config,
208 mut layouter: impl Layouter<F>,
209 ) -> Result<(), Error> {
210 let chip = DivChip::new_from_scratch(&config);
211
212 let x = chip.assign(&mut layouter, self.dividend)?;
213 cost_measure_start(&mut layouter);
214 let (q, r) = chip.div_rem(&mut layouter, &x, self.divisor.clone(), None)?;
215 cost_measure_end(&mut layouter);
216
217 chip.assert_equal_to_fixed(&mut layouter, &q, self.expected.0)?;
218 chip.assert_equal_to_fixed(&mut layouter, &r, self.expected.1)?;
219
220 chip.load_from_scratch(&mut layouter)
221 }
222 }
223
224 fn run<F, Assigned, DivChip>(
225 dividend: Assigned::Element,
226 divisor: BigUint,
227 expected: (Assigned::Element, Assigned::Element),
228 must_pass: bool,
229 cost_model: bool,
230 chip_name: &str,
231 ) where
232 F: CircuitField + FromUniformBytes<64> + Ord,
233 Assigned: InnerValue,
234 Assigned::Element: CircuitField,
235 DivChip: DivisionInstructions<F, Assigned> + FromScratch<F>,
236 {
237 let circuit = TestCircuit::<F, Assigned, DivChip> {
238 dividend: Value::known(dividend),
239 divisor,
240 expected,
241 _marker: PhantomData,
242 };
243
244 let public_inputs = vec![vec![], vec![]];
245 match MockProver::run(&circuit, public_inputs) {
246 Ok(prover) => match prover.verify() {
247 Ok(()) => assert!(must_pass),
248 Err(e) => assert!(!must_pass, "Failed verifier with error {e:?}"),
249 },
250 Err(e) => assert!(!must_pass, "Failed prover with error {e:?}"),
251 }
252
253 if cost_model {
254 circuit_to_json(chip_name, "div_rem", circuit);
255 }
256 }
257
258 pub fn test_div_rem<F, Assigned, DivChip>(chip_name: &str)
259 where
260 F: CircuitField + FromUniformBytes<64> + Ord,
261 Assigned: InnerValue,
262 Assigned::Element: CircuitField,
263 DivChip: DivisionInstructions<F, Assigned> + FromScratch<F>,
264 {
265 [
266 (17, 5, (3, 2), true),
267 (0, 1, (0, 0), true),
268 (1, 1, (1, 0), true),
269 (100, 5, (20, 0), true),
270 (100, 7, (14, 2), true),
271 (1 << 13, 1, (1 << 13, 0), true),
272 ]
273 .into_iter()
274 .enumerate()
275 .for_each(|(i, (dividend, divisor, (q, r), must_pass))| {
276 run::<F, Assigned, DivChip>(
277 Assigned::Element::from(dividend),
278 BigUint::from(divisor as u64),
279 (Assigned::Element::from(q), Assigned::Element::from(r)),
280 must_pass,
281 i == 0,
282 chip_name,
283 )
284 });
285
286 let zero = BigUint::from(0u64);
287 let one = BigUint::from(1u64);
288 let two = BigUint::from(2u64);
289 let max = (-Assigned::Element::from(1)).to_biguint();
290
291 [
292 (&max, &(&max - &one), (&one, &one), true),
293 (&(&max + &one), &(&max - &one), (&one, &two), false),
294 (&(&max + &one), &(&max - &one), (&zero, &zero), true),
295 ]
296 .into_iter()
297 .for_each(|(dividend, divisor, (q, r), must_pass)| {
298 run::<F, Assigned, DivChip>(
299 big_to_fe(dividend.clone()),
300 divisor.clone(),
301 (big_to_fe(q.clone()), big_to_fe(r.clone())),
302 must_pass,
303 false,
304 chip_name,
305 )
306 });
307 }
308}