1use crate::{
2 circuit_design::{
3 capabilities::{read_column_array, write_column_array_const, write_column_const},
4 ColAccessCap, ColWriteCap, LookupCap,
5 },
6 fec::{
7 columns::{FECColumn, FECColumnInput, FECColumnInter, FECColumnOutput},
8 lookups::LookupTable,
9 },
10 serialization::interpreter::{
11 bigint_to_biguint_f, combine_carry, combine_small_to_large, fold_choice2,
12 limb_decompose_biguint, limb_decompose_ff, LIMB_BITSIZE_LARGE, LIMB_BITSIZE_SMALL,
13 N_LIMBS_LARGE, N_LIMBS_SMALL,
14 },
15};
16use ark_ff::{PrimeField, Zero};
17use core::marker::PhantomData;
18use num_bigint::{BigInt, BigUint, ToBigInt};
19use num_integer::Integer;
20use o1_utils::field_helpers::FieldHelpers;
21
22pub fn limbs_to_bigints<F: PrimeField, const N: usize>(input: [F; N]) -> Vec<BigInt> {
24 input
25 .iter()
26 .map(|f| f.to_bigint_positive())
27 .collect::<Vec<_>>()
28}
29
30pub fn constrain_ec_addition<
199 F: PrimeField,
200 Ff: PrimeField,
201 Env: ColAccessCap<F, FECColumn> + LookupCap<F, FECColumn, LookupTable<Ff>>,
202>(
203 env: &mut Env,
204) {
205 let xp_limbs_large: [_; N_LIMBS_LARGE] =
206 read_column_array(env, |i| FECColumn::Input(FECColumnInput::XP(i)));
207 let yp_limbs_large: [_; N_LIMBS_LARGE] =
208 read_column_array(env, |i| FECColumn::Input(FECColumnInput::YP(i)));
209 let xq_limbs_large: [_; N_LIMBS_LARGE] =
210 read_column_array(env, |i| FECColumn::Input(FECColumnInput::XQ(i)));
211 let yq_limbs_large: [_; N_LIMBS_LARGE] =
212 read_column_array(env, |i| FECColumn::Input(FECColumnInput::YQ(i)));
213 let f_limbs_large: [_; N_LIMBS_LARGE] =
214 read_column_array(env, |i| FECColumn::Inter(FECColumnInter::F(i)));
215 let xr_limbs_small: [_; N_LIMBS_SMALL] =
216 read_column_array(env, |i| FECColumn::Output(FECColumnOutput::XR(i)));
217 let yr_limbs_small: [_; N_LIMBS_SMALL] =
218 read_column_array(env, |i| FECColumn::Output(FECColumnOutput::YR(i)));
219 let s_limbs_small: [_; N_LIMBS_SMALL] =
220 read_column_array(env, |i| FECColumn::Inter(FECColumnInter::S(i)));
221
222 let q1_limbs_small: [_; N_LIMBS_SMALL] =
223 read_column_array(env, |i| FECColumn::Inter(FECColumnInter::Q1(i)));
224 let q2_limbs_small: [_; N_LIMBS_SMALL] =
225 read_column_array(env, |i| FECColumn::Inter(FECColumnInter::Q2(i)));
226 let q3_limbs_small: [_; N_LIMBS_SMALL] =
227 read_column_array(env, |i| FECColumn::Inter(FECColumnInter::Q3(i)));
228 let q1_limbs_large: [_; N_LIMBS_LARGE] =
229 read_column_array(env, |i| FECColumn::Inter(FECColumnInter::Q1L(i)));
230 let q2_limbs_large: [_; N_LIMBS_LARGE] =
231 read_column_array(env, |i| FECColumn::Inter(FECColumnInter::Q2L(i)));
232 let q3_limbs_large: [_; N_LIMBS_LARGE] =
233 read_column_array(env, |i| FECColumn::Inter(FECColumnInter::Q3L(i)));
234
235 let q1_sign = env.read_column(FECColumn::Inter(FECColumnInter::Q1Sign));
236 let q2_sign = env.read_column(FECColumn::Inter(FECColumnInter::Q2Sign));
237 let q3_sign = env.read_column(FECColumn::Inter(FECColumnInter::Q3Sign));
238
239 let carry1_limbs_small: [_; 2 * N_LIMBS_SMALL + 2] =
240 read_column_array(env, |i| FECColumn::Inter(FECColumnInter::Carry1(i)));
241 let carry2_limbs_small: [_; 2 * N_LIMBS_SMALL + 2] =
242 read_column_array(env, |i| FECColumn::Inter(FECColumnInter::Carry2(i)));
243 let carry3_limbs_small: [_; 2 * N_LIMBS_SMALL + 2] =
244 read_column_array(env, |i| FECColumn::Inter(FECColumnInter::Carry3(i)));
245
246 let constant_u128 = |x: u128| -> Env::Variable { Env::constant(From::from(x)) };
250
251 for (i, x) in s_limbs_small
253 .iter()
254 .chain(xr_limbs_small.iter())
255 .chain(yr_limbs_small.iter())
256 .enumerate()
257 {
258 if i % N_LIMBS_SMALL == N_LIMBS_SMALL - 1 {
259 env.lookup(
261 LookupTable::RangeCheckFfHighest(PhantomData),
262 vec![x.clone()],
263 );
264 } else {
265 env.lookup(LookupTable::RangeCheck15, vec![x.clone()]);
266 }
267 }
268
269 for x in q1_limbs_small
271 .iter()
272 .chain(q2_limbs_small.iter())
273 .chain(q3_limbs_small.iter())
274 {
275 env.lookup(LookupTable::RangeCheck15, vec![x.clone()]);
276 }
277
278 for x in [&q1_sign, &q2_sign, &q3_sign] {
280 env.assert_zero(x.clone() * x.clone() - Env::constant(F::one()));
281 }
282
283 for (i, x) in carry1_limbs_small
285 .iter()
286 .chain(carry2_limbs_small.iter())
287 .chain(carry3_limbs_small.iter())
288 .enumerate()
289 {
290 if i % 6 == 5 {
291 env.lookup(LookupTable::RangeCheck9Abs, vec![x.clone()]);
294 } else {
295 env.lookup(LookupTable::RangeCheck14Abs, vec![x.clone()]);
296 }
297 }
298
299 {
301 let q1_limbs_large_abs_expected =
302 combine_small_to_large::<_, _, Env>(q1_limbs_small.clone());
303 for j in 0..N_LIMBS_LARGE {
304 env.assert_zero(
305 q1_limbs_large[j].clone()
306 - q1_sign.clone() * q1_limbs_large_abs_expected[j].clone(),
307 );
308 }
309 let q2_limbs_large_abs_expected =
310 combine_small_to_large::<_, _, Env>(q2_limbs_small.clone());
311 for j in 0..N_LIMBS_LARGE {
312 env.assert_zero(
313 q2_limbs_large[j].clone()
314 - q2_sign.clone() * q2_limbs_large_abs_expected[j].clone(),
315 );
316 }
317 let q3_limbs_large_abs_expected =
318 combine_small_to_large::<_, _, Env>(q3_limbs_small.clone());
319 for j in 0..N_LIMBS_LARGE {
320 env.assert_zero(
321 q3_limbs_large[j].clone()
322 - q3_sign.clone() * q3_limbs_large_abs_expected[j].clone(),
323 );
324 }
325 }
326
327 let xr_limbs_large = combine_small_to_large::<_, _, Env>(xr_limbs_small.clone());
328 let yr_limbs_large = combine_small_to_large::<_, _, Env>(yr_limbs_small.clone());
329 let s_limbs_large = combine_small_to_large::<_, _, Env>(s_limbs_small.clone());
330
331 let carry1_limbs_large: [_; 2 * N_LIMBS_LARGE - 2] =
332 combine_carry::<F, _, Env>(carry1_limbs_small.clone());
333 let carry2_limbs_large: [_; 2 * N_LIMBS_LARGE - 2] =
334 combine_carry::<F, _, Env>(carry2_limbs_small.clone());
335 let carry3_limbs_large: [_; 2 * N_LIMBS_LARGE - 2] =
336 combine_carry::<F, _, Env>(carry3_limbs_small.clone());
337
338 let limb_size_large = constant_u128(1u128 << LIMB_BITSIZE_LARGE);
339 let add_extra_carries =
340 |i: usize, carry_limbs_large: &[Env::Variable; 2 * N_LIMBS_LARGE - 2]| -> Env::Variable {
341 if i == 0 {
342 -(carry_limbs_large[0].clone() * limb_size_large.clone())
343 } else if i < 2 * N_LIMBS_LARGE - 2 {
344 carry_limbs_large[i - 1].clone()
345 - carry_limbs_large[i].clone() * limb_size_large.clone()
346 } else if i == 2 * N_LIMBS_LARGE - 2 {
347 carry_limbs_large[i - 1].clone()
348 } else {
349 panic!("add_extra_carries: the index {i:?} is too high")
350 }
351 };
352
353 for i in 0..2 * N_LIMBS_LARGE - 1 {
357 let mut constraint1 = fold_choice2(N_LIMBS_LARGE, i, |j, k| {
358 s_limbs_large[j].clone() * (xp_limbs_large[k].clone() - xq_limbs_large[k].clone())
359 });
360 if i < N_LIMBS_LARGE {
361 constraint1 = constraint1 - (yp_limbs_large[i].clone() - yq_limbs_large[i].clone());
362 }
363 constraint1 = constraint1
364 - fold_choice2(N_LIMBS_LARGE, i, |j, k| {
365 q1_limbs_large[j].clone() * f_limbs_large[k].clone()
366 });
367 constraint1 = constraint1 + add_extra_carries(i, &carry1_limbs_large);
368 env.assert_zero(constraint1);
369 }
370
371 for i in 0..2 * N_LIMBS_LARGE - 1 {
374 let mut constraint2 = -fold_choice2(N_LIMBS_LARGE, i, |j, k| {
375 s_limbs_large[j].clone() * s_limbs_large[k].clone()
376 });
377 if i < N_LIMBS_LARGE {
378 constraint2 = constraint2
379 + xr_limbs_large[i].clone()
380 + xp_limbs_large[i].clone()
381 + xq_limbs_large[i].clone();
382 }
383 constraint2 = constraint2
384 - fold_choice2(N_LIMBS_LARGE, i, |j, k| {
385 q2_limbs_large[j].clone() * f_limbs_large[k].clone()
386 });
387 constraint2 = constraint2 + add_extra_carries(i, &carry2_limbs_large);
388 env.assert_zero(constraint2);
389 }
390
391 for i in 0..2 * N_LIMBS_LARGE - 1 {
394 let mut constraint3 = -fold_choice2(N_LIMBS_LARGE, i, |j, k| {
395 s_limbs_large[j].clone() * (xp_limbs_large[k].clone() - xr_limbs_large[k].clone())
396 });
397 if i < N_LIMBS_LARGE {
398 constraint3 = constraint3 + yr_limbs_large[i].clone() + yp_limbs_large[i].clone();
399 }
400 constraint3 = constraint3
401 - fold_choice2(N_LIMBS_LARGE, i, |j, k| {
402 q3_limbs_large[j].clone() * f_limbs_large[k].clone()
403 });
404 constraint3 = constraint3 + add_extra_carries(i, &carry3_limbs_large);
405 env.assert_zero(constraint3)
406 }
407}
408
409pub fn ec_add_circuit<
415 F: PrimeField,
416 Ff: PrimeField,
417 Env: ColWriteCap<F, FECColumn> + LookupCap<F, FECColumn, LookupTable<Ff>>,
418>(
419 env: &mut Env,
420 xp: Ff,
421 yp: Ff,
422 xq: Ff,
423 yq: Ff,
424) -> (Ff, Ff) {
425 let slope: Ff = (yq - yp) / (xq - xp);
426 let xr: Ff = slope * slope - xp - xq;
427 let yr: Ff = slope * (xp - xr) - yp;
428
429 let two_bi: BigInt = BigInt::from(2);
430
431 let large_limb_size: F = From::from(1u128 << LIMB_BITSIZE_LARGE);
432
433 let f_bui: BigUint = TryFrom::try_from(Ff::MODULUS).unwrap();
435 let f_bi: BigInt = f_bui.to_bigint().unwrap();
436
437 let n_bui: BigUint = TryFrom::try_from(F::MODULUS).unwrap();
439 let n_bi: BigInt = n_bui.to_bigint().unwrap();
440 let n_half_bi = &n_bi / &two_bi;
441
442 let xp_limbs_large: [F; N_LIMBS_LARGE] =
443 limb_decompose_ff::<F, Ff, LIMB_BITSIZE_LARGE, N_LIMBS_LARGE>(&xp);
444 let yp_limbs_large: [F; N_LIMBS_LARGE] =
445 limb_decompose_ff::<F, Ff, LIMB_BITSIZE_LARGE, N_LIMBS_LARGE>(&yp);
446 let xq_limbs_large: [F; N_LIMBS_LARGE] =
447 limb_decompose_ff::<F, Ff, LIMB_BITSIZE_LARGE, N_LIMBS_LARGE>(&xq);
448 let yq_limbs_large: [F; N_LIMBS_LARGE] =
449 limb_decompose_ff::<F, Ff, LIMB_BITSIZE_LARGE, N_LIMBS_LARGE>(&yq);
450 let f_limbs_large: [F; N_LIMBS_LARGE] =
451 limb_decompose_biguint::<F, LIMB_BITSIZE_LARGE, N_LIMBS_LARGE>(f_bui.clone());
452 let xr_limbs_large: [F; N_LIMBS_LARGE] =
453 limb_decompose_ff::<F, Ff, LIMB_BITSIZE_LARGE, N_LIMBS_LARGE>(&xr);
454 let yr_limbs_large: [F; N_LIMBS_LARGE] =
455 limb_decompose_ff::<F, Ff, LIMB_BITSIZE_LARGE, N_LIMBS_LARGE>(&yr);
456
457 let xr_limbs_small: [F; N_LIMBS_SMALL] =
458 limb_decompose_ff::<F, Ff, LIMB_BITSIZE_SMALL, N_LIMBS_SMALL>(&xr);
459 let yr_limbs_small: [F; N_LIMBS_SMALL] =
460 limb_decompose_ff::<F, Ff, LIMB_BITSIZE_SMALL, N_LIMBS_SMALL>(&yr);
461 let slope_limbs_small: [F; N_LIMBS_SMALL] =
462 limb_decompose_ff::<F, Ff, LIMB_BITSIZE_SMALL, N_LIMBS_SMALL>(&slope);
463 let slope_limbs_large: [F; N_LIMBS_LARGE] =
464 limb_decompose_ff::<F, Ff, LIMB_BITSIZE_LARGE, N_LIMBS_LARGE>(&slope);
465
466 write_column_array_const(env, &xp_limbs_large, |i| {
467 FECColumn::Input(FECColumnInput::XP(i))
468 });
469 write_column_array_const(env, &yp_limbs_large, |i| {
470 FECColumn::Input(FECColumnInput::YP(i))
471 });
472 write_column_array_const(env, &xq_limbs_large, |i| {
473 FECColumn::Input(FECColumnInput::XQ(i))
474 });
475 write_column_array_const(env, &yq_limbs_large, |i| {
476 FECColumn::Input(FECColumnInput::YQ(i))
477 });
478 write_column_array_const(env, &f_limbs_large, |i| {
479 FECColumn::Inter(FECColumnInter::F(i))
480 });
481 write_column_array_const(env, &xr_limbs_small, |i| {
482 FECColumn::Output(FECColumnOutput::XR(i))
483 });
484 write_column_array_const(env, &yr_limbs_small, |i| {
485 FECColumn::Output(FECColumnOutput::YR(i))
486 });
487 write_column_array_const(env, &slope_limbs_small, |i| {
488 FECColumn::Inter(FECColumnInter::S(i))
489 });
490
491 let xp_bi: BigInt = FieldHelpers::to_bigint_positive(&xp);
492 let yp_bi: BigInt = FieldHelpers::to_bigint_positive(&yp);
493 let xq_bi: BigInt = FieldHelpers::to_bigint_positive(&xq);
494 let yq_bi: BigInt = FieldHelpers::to_bigint_positive(&yq);
495 let slope_bi: BigInt = FieldHelpers::to_bigint_positive(&slope);
496 let xr_bi: BigInt = FieldHelpers::to_bigint_positive(&xr);
497 let yr_bi: BigInt = FieldHelpers::to_bigint_positive(&yr);
498
499 let (q1_bi, r1_bi) = (&slope_bi * (&xp_bi - &xq_bi) - (&yp_bi - &yq_bi)).div_rem(&f_bi);
501 assert!(r1_bi.is_zero());
502 let (q1_bi, q1_sign): (BigInt, F) = if q1_bi < BigInt::zero() {
504 (-q1_bi, -F::one())
505 } else {
506 (q1_bi, F::one())
507 };
508
509 let (q2_bi, r2_bi) = (&xr_bi - &slope_bi * &slope_bi + &xp_bi + &xq_bi).div_rem(&f_bi);
511 assert!(r2_bi.is_zero());
512 let (q2_bi, q2_sign): (BigInt, F) = if q2_bi < BigInt::zero() {
513 (-q2_bi, -F::one())
514 } else {
515 (q2_bi, F::one())
516 };
517
518 let (q3_bi, r3_bi) = (&yr_bi + &yp_bi - &slope_bi * (&xp_bi - &xr_bi)).div_rem(&f_bi);
520 assert!(r3_bi.is_zero());
521 let (q3_bi, q3_sign): (BigInt, F) = if q3_bi < BigInt::zero() {
522 (-q3_bi, -F::one())
523 } else {
524 (q3_bi, F::one())
525 };
526
527 let q1_limbs_large: [F; N_LIMBS_LARGE] =
531 limb_decompose_biguint::<F, LIMB_BITSIZE_LARGE, N_LIMBS_LARGE>(q1_bi.to_biguint().unwrap())
532 .into_iter()
533 .map(|v| v * q1_sign)
534 .collect::<Vec<_>>()
535 .try_into()
536 .unwrap();
537 let q2_limbs_large: [F; N_LIMBS_LARGE] =
538 limb_decompose_biguint::<F, LIMB_BITSIZE_LARGE, N_LIMBS_LARGE>(q2_bi.to_biguint().unwrap())
539 .into_iter()
540 .map(|v| v * q2_sign)
541 .collect::<Vec<_>>()
542 .try_into()
543 .unwrap();
544 let q3_limbs_large: [F; N_LIMBS_LARGE] =
545 limb_decompose_biguint::<F, LIMB_BITSIZE_LARGE, N_LIMBS_LARGE>(q3_bi.to_biguint().unwrap())
546 .into_iter()
547 .map(|v| v * q3_sign)
548 .collect::<Vec<_>>()
549 .try_into()
550 .unwrap();
551
552 let q1_limbs_small: [F; N_LIMBS_SMALL] =
555 limb_decompose_biguint::<F, LIMB_BITSIZE_SMALL, N_LIMBS_SMALL>(q1_bi.to_biguint().unwrap());
556 let q2_limbs_small: [F; N_LIMBS_SMALL] =
557 limb_decompose_biguint::<F, LIMB_BITSIZE_SMALL, N_LIMBS_SMALL>(q2_bi.to_biguint().unwrap());
558 let q3_limbs_small: [F; N_LIMBS_SMALL] =
559 limb_decompose_biguint::<F, LIMB_BITSIZE_SMALL, N_LIMBS_SMALL>(q3_bi.to_biguint().unwrap());
560
561 write_column_array_const(env, &q1_limbs_small, |i| {
562 FECColumn::Inter(FECColumnInter::Q1(i))
563 });
564 write_column_array_const(env, &q2_limbs_small, |i| {
565 FECColumn::Inter(FECColumnInter::Q2(i))
566 });
567 write_column_array_const(env, &q3_limbs_small, |i| {
568 FECColumn::Inter(FECColumnInter::Q3(i))
569 });
570
571 write_column_const(env, FECColumn::Inter(FECColumnInter::Q1Sign), &q1_sign);
572 write_column_const(env, FECColumn::Inter(FECColumnInter::Q2Sign), &q2_sign);
573 write_column_const(env, FECColumn::Inter(FECColumnInter::Q3Sign), &q3_sign);
574
575 write_column_array_const(env, &q1_limbs_large, |i| {
576 FECColumn::Inter(FECColumnInter::Q1L(i))
577 });
578 write_column_array_const(env, &q2_limbs_large, |i| {
579 FECColumn::Inter(FECColumnInter::Q2L(i))
580 });
581 write_column_array_const(env, &q3_limbs_large, |i| {
582 FECColumn::Inter(FECColumnInter::Q3L(i))
583 });
584
585 let mut carry1: F = From::from(0u64);
586 let mut carry2: F = From::from(0u64);
587 let mut carry3: F = From::from(0u64);
588
589 for i in 0..N_LIMBS_LARGE * 2 - 1 {
590 let compute_carry = |res: F| -> F {
591 let mut res_bi = res.to_bigint_positive();
593 if res_bi > n_half_bi {
594 res_bi -= &n_bi;
595 }
596 let (div, rem) = res_bi.div_rem(&large_limb_size.to_bigint_positive());
597 assert!(
598 rem.is_zero(),
599 "Cannot compute carry for step {i:?}: div {div:?}, rem {rem:?}"
600 );
601 let carry_f: BigUint = bigint_to_biguint_f(div, &n_bi);
602 F::from_biguint(&carry_f).unwrap()
603 };
604
605 fn assign_carry<F, Env, ColMap>(
606 env: &mut Env,
607 n_half_bi: &BigInt,
608 i: usize,
609 newcarry: F,
610 carryvar: &mut F,
611 column_mapper: ColMap,
612 ) where
613 F: PrimeField,
614 Env: ColWriteCap<F, FECColumn>,
615 ColMap: Fn(usize) -> FECColumn,
616 {
617 if i < N_LIMBS_LARGE * 2 - 2 {
619 let newcarry_sign = if &newcarry.to_bigint_positive() > n_half_bi {
621 F::zero() - F::one()
622 } else {
623 F::one()
624 };
625 let newcarry_abs_bui = (newcarry * newcarry_sign).to_biguint();
626 let newcarry_limbs: [F; 6] =
629 limb_decompose_biguint::<F, { LIMB_BITSIZE_SMALL - 1 }, 6>(
630 newcarry_abs_bui.clone(),
631 );
632
633 for (j, limb) in newcarry_limbs.iter().enumerate() {
634 write_column_const(env, column_mapper(6 * i + j), &(newcarry_sign * limb));
635 }
636
637 *carryvar = newcarry;
638 } else {
639 assert!(newcarry.is_zero(), "Last carry is non-zero");
641 }
642 }
643
644 let mut res1 = fold_choice2(N_LIMBS_LARGE, i, |j, k| {
646 slope_limbs_large[j] * (xp_limbs_large[k] - xq_limbs_large[k])
647 });
648 if i < N_LIMBS_LARGE {
649 res1 -= yp_limbs_large[i] - yq_limbs_large[i];
650 }
651 res1 -= fold_choice2(N_LIMBS_LARGE, i, |j, k| {
652 q1_limbs_large[j] * f_limbs_large[k]
653 });
654 res1 += carry1;
655 let newcarry1 = compute_carry(res1);
656 assign_carry(env, &n_half_bi, i, newcarry1, &mut carry1, |i| {
657 FECColumn::Inter(FECColumnInter::Carry1(i))
658 });
659
660 let mut res2 = F::zero();
662 res2 -= fold_choice2(N_LIMBS_LARGE, i, |j, k| {
663 slope_limbs_large[j] * slope_limbs_large[k]
664 });
665 if i < N_LIMBS_LARGE {
666 res2 += xr_limbs_large[i] + xp_limbs_large[i] + xq_limbs_large[i];
667 }
668 res2 -= fold_choice2(N_LIMBS_LARGE, i, |j, k| {
669 q2_limbs_large[j] * f_limbs_large[k]
670 });
671 res2 += carry2;
672 let newcarry2 = compute_carry(res2);
673 assign_carry(env, &n_half_bi, i, newcarry2, &mut carry2, |i| {
674 FECColumn::Inter(FECColumnInter::Carry2(i))
675 });
676
677 let mut res3 = F::zero();
679 res3 -= fold_choice2(N_LIMBS_LARGE, i, |j, k| {
680 slope_limbs_large[j] * (xp_limbs_large[k] - xr_limbs_large[k])
681 });
682 if i < N_LIMBS_LARGE {
683 res3 += yr_limbs_large[i] + yp_limbs_large[i];
684 }
685 res3 -= fold_choice2(N_LIMBS_LARGE, i, |j, k| {
686 q3_limbs_large[j] * f_limbs_large[k]
687 });
688 res3 += carry3;
689 let newcarry3 = compute_carry(res3);
690 assign_carry(env, &n_half_bi, i, newcarry3, &mut carry3, |i| {
691 FECColumn::Inter(FECColumnInter::Carry3(i))
692 });
693 }
694
695 constrain_ec_addition::<F, Ff, Env>(env);
696
697 (xr, yr)
698}