Skip to main content

midnight_circuits/ecc/foreign/
weierstrass_chip.rs

1// This file is part of MIDNIGHT-ZK.
2// Copyright (C) Midnight Foundation
3// SPDX-License-Identifier: Apache-2.0
4// Licensed under the Apache License, Version 2.0 (the "License");
5// You may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7// http://www.apache.org/licenses/LICENSE-2.0
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14//! Elliptic curve (in Weierstrass form) operations over foreign fields.
15//! This module supports curves of the form y^2 = x^3 + b (i.e. with a = 0).
16//!
17//! We require that the emulated elliptic curve do not have low-order points.
18//! In particular, the curve (or the relevant subgroup) must have a large prime
19//! order.
20
21use std::{
22    cell::RefCell,
23    cmp::max,
24    collections::HashMap,
25    fmt::Debug,
26    hash::{Hash, Hasher},
27    rc::Rc,
28};
29
30use ff::{Field, PrimeField};
31use group::Group;
32use midnight_proofs::{
33    circuit::{Chip, Layouter, Value},
34    plonk::{Advice, Column, ConstraintSystem, Error, Fixed, Selector},
35};
36use num_bigint::BigUint;
37use num_traits::One;
38use rand::rngs::OsRng;
39#[cfg(any(test, feature = "testing"))]
40use {
41    crate::testing_utils::Sampleable, crate::utils::util::FromScratch,
42    midnight_proofs::plonk::Instance, rand::RngCore,
43};
44
45use super::gates::weierstrass::{
46    lambda_squared,
47    lambda_squared::LambdaSquaredConfig,
48    on_curve,
49    on_curve::OnCurveConfig,
50    slope::{self, SlopeConfig},
51    tangent,
52    tangent::TangentConfig,
53};
54use crate::{
55    ecc::{
56        curves::WeierstrassCurve,
57        foreign::common::{
58            add_1bit_scalar_bases, configure_multi_select_lookup, fill_dynamic_lookup_row,
59            msm_preprocess,
60        },
61    },
62    field::foreign::{
63        field_chip::{FieldChip, FieldChipConfig},
64        params::FieldEmulationParams,
65    },
66    instructions::{
67        ArithInstructions, AssertionInstructions, AssignmentInstructions, ControlFlowInstructions,
68        DecompositionInstructions, EccInstructions, EqualityInstructions, NativeInstructions,
69        PublicInputInstructions, ScalarFieldInstructions, ZeroInstructions,
70    },
71    types::{AssignedBit, AssignedField, AssignedNative, InnerConstants, InnerValue, Instantiable},
72    utils::util::{big_to_fe, bigint_to_fe, glv_scalar_decomposition},
73    CircuitField,
74};
75
76/// Foreign Weierstrass ECC configuration.
77#[derive(Clone, Debug)]
78pub struct ForeignWeierstrassEccConfig<C>
79where
80    C: WeierstrassCurve,
81{
82    base_field_config: FieldChipConfig,
83    on_curve_config: on_curve::OnCurveConfig<C>,
84    slope_config: slope::SlopeConfig<C>,
85    tangent_config: tangent::TangentConfig<C>,
86    lambda_squared_config: lambda_squared::LambdaSquaredConfig<C>,
87    // columns for the dynamic lookup
88    q_multi_select: Selector,
89    idx_col_multi_select: Column<Advice>,
90    tag_col_multi_select: Column<Fixed>,
91}
92
93/// Number of columns required by the custom gates of this chip.
94pub fn nb_foreign_ecc_chip_columns<F, C, B, S>() -> usize
95where
96    F: CircuitField,
97    C: WeierstrassCurve,
98    B: FieldEmulationParams<F, C::Base>,
99{
100    // The scalar field is treated as a gadget. Here we only account for the columns
101    // that this chip requires for its own custom gates.
102    // The 2 in `2 + |moduli|` corresponds to `u_col` + `cond_col`.
103    // The outer `+ 1` corresponds to the advice column for the index of
104    // `multi_select`.
105    B::NB_LIMBS as usize + max(B::NB_LIMBS as usize, 2 + B::moduli().len()) + 1
106}
107
108/// Shared MSM randomness for the windowed double-and-add algorithm.
109///
110/// In the windowed MSM, a random point `r` is used to randomize the
111/// double-and-add accumulator, ensuring that intermediate values do not
112/// collide with table entries (in particular, avoiding equal x-coordinates
113/// or identity points). This allows the use of incomplete addition
114/// throughout the loop, which is cheaper than complete addition.
115///
116/// The value of `r` can be chosen by the prover (who will choose it
117/// uniformly at random for statistical completeness) and this is not a
118/// problem for soundness.
119///
120/// By sharing `r` (and thus `α = (2^WS - 1) * r`) across all MSM calls on
121/// the same chip, precomputed tables for the same base point can be reused
122/// across MSM invocations. Sharing the randomness across all MSMs hinders
123/// completeness, but only by a polynomial factor: we still get statistical
124/// completeness (i.e. completeness with overwhelming probability over the
125/// choice of `r`).
126#[derive(Clone, Debug)]
127struct MsmRandomness<F, C, B>
128where
129    F: CircuitField,
130    C: WeierstrassCurve,
131    B: FieldEmulationParams<F, C::Base>,
132{
133    r: AssignedForeignPoint<F, C, B>,
134    neg_alpha: AssignedForeignPoint<F, C, B>,
135}
136
137/// Map from window size to the corresponding [`MsmRandomness`], lazily
138/// populated on first MSM call for each window size.
139type MsmRandomnessMap<F, C, B> = HashMap<usize, MsmRandomness<F, C, B>>;
140
141/// ['ECChip'] to perform foreign Weierstrass EC operations.
142#[derive(Clone, Debug)]
143pub struct ForeignWeierstrassEccChip<F, C, B, S, N>
144where
145    F: CircuitField,
146    C: WeierstrassCurve,
147    B: FieldEmulationParams<F, C::Base>,
148    S: ScalarFieldInstructions<F>,
149    S::Scalar: InnerValue<Element = C::ScalarField>,
150    N: NativeInstructions<F>,
151{
152    config: ForeignWeierstrassEccConfig<C>,
153    native_gadget: N,
154    base_field_chip: FieldChip<F, C::Base, B, N>,
155    scalar_field_chip: S,
156    // A table tag counter to make sure all dynamic lookup tables are independent.
157    // This counter is always increased after loading a new table.
158    // It will never overflow unless you include more than 2^64 tables, will you?
159    // Even in that case, we would get a compile-time error.
160    tag_cnt: Rc<RefCell<u64>>,
161    // Shared random point `r` for the windowed MSM double-and-add algorithm.
162    // The value of `r` can be chosen by the prover (who will choose it uniformly
163    // at random for statistical completeness) and this is not a problem for
164    // soundness.
165    // Lazily initialized on first MSM call. By sharing `r` across MSM calls,
166    // computations depending on it e.g. α can be cached and reused.
167    // Importantly, such cache is keyed by window size.
168    msm_randomness: Rc<RefCell<MsmRandomnessMap<F, C, B>>>,
169    // A random point used in windowed_msm (to shift the initial accumulator) so that the
170    // double-and-add loop can internally use incomplete addition. Sampled once at chip
171    // construction time.
172    random_point: C::CryptographicGroup,
173}
174
175/// Type for foreign EC points.
176/// The identity is represented with field `is_id`, whose value is `1` iff the
177/// point is the identity. If `is_id` is set, the values of `x` and `y` are
178/// irrelevant and can be anything.
179/// x2 is a ModInt encoding the square of x, which is computed once and stored.
180/// This value is used by our custom gates, it allows us to implement all
181/// custom gates without degree-3 terms, so we can reuse the same auxiliary
182/// moduli as for ModArith.mul.
183#[derive(Clone, Debug)]
184#[must_use]
185pub struct AssignedForeignPoint<F, C, B>
186where
187    F: CircuitField,
188    C: WeierstrassCurve,
189    B: FieldEmulationParams<F, C::Base>,
190{
191    point: Value<C::CryptographicGroup>,
192    is_id: AssignedBit<F>,
193    x: AssignedField<F, C::Base, B>,
194    y: AssignedField<F, C::Base, B>,
195}
196
197impl<F, C, B> PartialEq for AssignedForeignPoint<F, C, B>
198where
199    F: CircuitField,
200    C: WeierstrassCurve,
201    B: FieldEmulationParams<F, C::Base>,
202{
203    fn eq(&self, other: &Self) -> bool {
204        self.is_id == other.is_id && self.x == other.x && self.y == other.y
205    }
206}
207
208impl<F, C, B> Eq for AssignedForeignPoint<F, C, B>
209where
210    F: CircuitField,
211    C: WeierstrassCurve,
212    B: FieldEmulationParams<F, C::Base>,
213{
214}
215
216impl<F, C, B> Hash for AssignedForeignPoint<F, C, B>
217where
218    F: CircuitField,
219    C: WeierstrassCurve,
220    B: FieldEmulationParams<F, C::Base>,
221{
222    fn hash<H: Hasher>(&self, state: &mut H) {
223        self.is_id.hash(state);
224        self.x.hash(state);
225        self.y.hash(state);
226    }
227}
228
229impl<F, C, B> Instantiable<F> for AssignedForeignPoint<F, C, B>
230where
231    F: CircuitField,
232    C: WeierstrassCurve,
233    B: FieldEmulationParams<F, C::Base>,
234{
235    fn as_public_input(p: &C::CryptographicGroup) -> Vec<F> {
236        let (x, y) = (*p).into().coordinates().unwrap_or((C::Base::ZERO, C::Base::ZERO));
237        let mut pis = [
238            AssignedField::<F, C::Base, B>::as_public_input(&x).as_slice(),
239            AssignedField::<F, C::Base, B>::as_public_input(&y).as_slice(),
240        ]
241        .concat();
242
243        let is_id: bool = p.is_identity().into();
244        pis.push(F::from(is_id as u64));
245
246        pis
247    }
248
249    fn from_public_input(fields: &[F]) -> Option<C::CryptographicGroup> {
250        if *fields.last()? == F::ONE {
251            return Some(C::CryptographicGroup::identity());
252        }
253        let nb_limbs_per_batch = (F::CAPACITY / B::LOG2_BASE) as usize;
254        let nb_pi_per_coord = B::NB_LIMBS.div_ceil(nb_limbs_per_batch as u32) as usize;
255        if fields.len() != 2 * nb_pi_per_coord + 1 {
256            return None;
257        }
258        let x = AssignedField::<F, C::Base, B>::from_public_input(&fields[..nb_pi_per_coord])?;
259        let y = AssignedField::<F, C::Base, B>::from_public_input(
260            &fields[nb_pi_per_coord..nb_pi_per_coord * 2],
261        )?;
262        C::from_xy(x, y).map(|p| p.into_subgroup())
263    }
264}
265
266impl<F, C, B> InnerValue for AssignedForeignPoint<F, C, B>
267where
268    F: CircuitField,
269    C: WeierstrassCurve,
270    B: FieldEmulationParams<F, C::Base>,
271{
272    type Element = C::CryptographicGroup;
273
274    fn value(&self) -> Value<Self::Element> {
275        self.point
276    }
277}
278
279impl<F, C, B> InnerConstants for AssignedForeignPoint<F, C, B>
280where
281    F: CircuitField,
282    C: WeierstrassCurve,
283    B: FieldEmulationParams<F, C::Base>,
284{
285    fn inner_zero() -> C::CryptographicGroup {
286        C::CryptographicGroup::identity()
287    }
288
289    fn inner_one() -> Self::Element {
290        C::CryptographicGroup::generator()
291    }
292}
293
294#[cfg(any(test, feature = "testing"))]
295impl<F, C, B> Sampleable for AssignedForeignPoint<F, C, B>
296where
297    F: CircuitField,
298    C: WeierstrassCurve,
299    B: FieldEmulationParams<F, C::Base>,
300{
301    fn sample_inner(rng: impl RngCore) -> C::CryptographicGroup {
302        C::CryptographicGroup::random(rng)
303    }
304}
305
306impl<F, C, B, S, N> Chip<F> for ForeignWeierstrassEccChip<F, C, B, S, N>
307where
308    F: CircuitField,
309    C: WeierstrassCurve,
310    B: FieldEmulationParams<F, C::Base>,
311    S: ScalarFieldInstructions<F>,
312    S::Scalar: InnerValue<Element = C::ScalarField>,
313    N: NativeInstructions<F>,
314{
315    type Config = ForeignWeierstrassEccConfig<C>;
316    type Loaded = ();
317    fn config(&self) -> &Self::Config {
318        &self.config
319    }
320    fn loaded(&self) -> &Self::Loaded {
321        &()
322    }
323}
324
325impl<F, C, B, S, N> AssignmentInstructions<F, AssignedForeignPoint<F, C, B>>
326    for ForeignWeierstrassEccChip<F, C, B, S, N>
327where
328    F: CircuitField,
329    C: WeierstrassCurve,
330    B: FieldEmulationParams<F, C::Base>,
331    S: ScalarFieldInstructions<F>,
332    S::Scalar: InnerValue<Element = C::ScalarField>,
333    N: NativeInstructions<F>,
334{
335    /// Assigns a private curve point and enforces in-circuit that it is on the
336    /// curve and lies in the prime-order subgroup.
337    ///
338    /// If you deliberately need to skip the subgroup check, use
339    /// [`EccInstructions::assign_without_subgroup_check`] instead.
340    fn assign(
341        &self,
342        layouter: &mut impl Layouter<F>,
343        value: Value<C::CryptographicGroup>,
344    ) -> Result<AssignedForeignPoint<F, C, B>, Error> {
345        if C::COFACTOR > 1 {
346            let cofactor = C::ScalarField::from_u128(C::COFACTOR);
347            // Exhibit a cofactor-root Q and assert h * Q = p in-circuit.
348            // This guarantees that p ∈ C::CryptographicGroup = h · E(Fp).
349            let cofactor_root = self.assign_without_subgroup_check(
350                layouter,
351                value.map(|point| point * cofactor.invert().unwrap()),
352            )?;
353            self.mul_by_constant(layouter, cofactor, &cofactor_root)
354        } else {
355            self.assign_without_subgroup_check(layouter, value)
356        }
357    }
358
359    fn assign_fixed(
360        &self,
361        layouter: &mut impl Layouter<F>,
362        constant: C::CryptographicGroup,
363    ) -> Result<AssignedForeignPoint<F, C, B>, Error> {
364        let (xv, yv, is_id_value) = if C::CryptographicGroup::is_identity(&constant).into() {
365            (C::Base::ZERO, C::Base::ZERO, true)
366        } else {
367            let coordinates = constant
368                .into()
369                .coordinates()
370                .expect("assign_point_unchecked: invalid point given");
371            (coordinates.0, coordinates.1, false)
372        };
373        let is_id = self.native_gadget.assign_fixed(layouter, is_id_value)?;
374        let x = self.base_field_chip().assign_fixed(layouter, xv)?;
375        let y = self.base_field_chip().assign_fixed(layouter, yv)?;
376        let p = AssignedForeignPoint::<F, C, B> {
377            point: Value::known(constant),
378            is_id,
379            x,
380            y,
381        };
382        Ok(p)
383    }
384}
385
386impl<F, C, B, S, N> PublicInputInstructions<F, AssignedForeignPoint<F, C, B>>
387    for ForeignWeierstrassEccChip<F, C, B, S, N>
388where
389    F: CircuitField,
390    C: WeierstrassCurve,
391    B: FieldEmulationParams<F, C::Base>,
392    S: ScalarFieldInstructions<F>,
393    S::Scalar: InnerValue<Element = C::ScalarField>,
394    N: NativeInstructions<F> + PublicInputInstructions<F, AssignedBit<F>>,
395{
396    fn as_public_input(
397        &self,
398        layouter: &mut impl Layouter<F>,
399        p: &AssignedForeignPoint<F, C, B>,
400    ) -> Result<Vec<AssignedNative<F>>, Error> {
401        let mut pis = [
402            self.base_field_chip.as_public_input(layouter, &p.x)?.as_slice(),
403            self.base_field_chip.as_public_input(layouter, &p.y)?.as_slice(),
404        ]
405        .concat();
406
407        pis.push(p.is_id.clone().into());
408
409        Ok(pis)
410    }
411
412    fn constrain_as_public_input(
413        &self,
414        layouter: &mut impl Layouter<F>,
415        assigned: &AssignedForeignPoint<F, C, B>,
416    ) -> Result<(), Error> {
417        self.as_public_input(layouter, assigned)?
418            .iter()
419            .try_for_each(|c| self.native_gadget.constrain_as_public_input(layouter, c))
420    }
421
422    fn assign_as_public_input(
423        &self,
424        layouter: &mut impl Layouter<F>,
425        value: Value<C::CryptographicGroup>,
426    ) -> Result<AssignedForeignPoint<F, C, B>, Error> {
427        // Given our optimized way of constraining a point as public input, we
428        // cannot optimize the direct assignment as PI. We just compose `assign`
429        // with `constrain_as_public_input`.
430        let point = self.assign_without_subgroup_check(layouter, value)?;
431        self.constrain_as_public_input(layouter, &point)?;
432        Ok(point)
433    }
434}
435
436/// Inherit assignment instructions for [AssignedNative], from the
437/// `scalar_field_chip` when the scalar field is the same as the SNARK native
438/// field.
439/// Mind the binding `S: ScalarFieldInstructions<F, Scalar = AssignedNative<F>>`
440/// of this implementation.
441impl<F, C, B, S, N> AssignmentInstructions<F, AssignedNative<F>>
442    for ForeignWeierstrassEccChip<F, C, B, S, N>
443where
444    F: CircuitField,
445    C: WeierstrassCurve,
446    B: FieldEmulationParams<F, C::Base>,
447    S: ScalarFieldInstructions<F, Scalar = AssignedNative<F>>,
448    S::Scalar: InnerValue<Element = C::ScalarField>,
449    N: NativeInstructions<F>,
450{
451    fn assign(
452        &self,
453        layouter: &mut impl Layouter<F>,
454        value: Value<<S::Scalar as InnerValue>::Element>,
455    ) -> Result<S::Scalar, Error> {
456        self.scalar_field_chip().assign(layouter, value)
457    }
458
459    fn assign_fixed(
460        &self,
461        layouter: &mut impl Layouter<F>,
462        constant: <S::Scalar as InnerValue>::Element,
463    ) -> Result<S::Scalar, Error> {
464        self.scalar_field_chip().assign_fixed(layouter, constant)
465    }
466}
467
468/// Inherit assignment instructions for [AssignedField], from the
469/// `scalar_field_chip` when the emulated field is the scalar field.
470/// Mind the binding `S: ScalarFieldInstructions<F, Scalar = AssignedField<F,
471/// C::ScalarField>>` of this implementation.
472impl<F, C, B, S, SP, N> AssignmentInstructions<F, AssignedField<F, C::ScalarField, SP>>
473    for ForeignWeierstrassEccChip<F, C, B, S, N>
474where
475    F: CircuitField,
476    C: WeierstrassCurve,
477    B: FieldEmulationParams<F, C::Base>,
478    S: ScalarFieldInstructions<F, Scalar = AssignedField<F, C::ScalarField, SP>>,
479    S::Scalar: InnerValue<Element = C::ScalarField>,
480    SP: FieldEmulationParams<F, C::ScalarField>,
481    N: NativeInstructions<F>,
482{
483    fn assign(
484        &self,
485        layouter: &mut impl Layouter<F>,
486        value: Value<<S::Scalar as InnerValue>::Element>,
487    ) -> Result<S::Scalar, Error> {
488        self.scalar_field_chip().assign(layouter, value)
489    }
490
491    fn assign_fixed(
492        &self,
493        layouter: &mut impl Layouter<F>,
494        constant: <S::Scalar as InnerValue>::Element,
495    ) -> Result<S::Scalar, Error> {
496        self.scalar_field_chip().assign_fixed(layouter, constant)
497    }
498}
499
500impl<F, C, B, S, N> AssertionInstructions<F, AssignedForeignPoint<F, C, B>>
501    for ForeignWeierstrassEccChip<F, C, B, S, N>
502where
503    F: CircuitField,
504    C: WeierstrassCurve,
505    B: FieldEmulationParams<F, C::Base>,
506    S: ScalarFieldInstructions<F>,
507    S::Scalar: InnerValue<Element = C::ScalarField>,
508    N: NativeInstructions<F>,
509{
510    fn assert_equal(
511        &self,
512        layouter: &mut impl Layouter<F>,
513        p: &AssignedForeignPoint<F, C, B>,
514        q: &AssignedForeignPoint<F, C, B>,
515    ) -> Result<(), Error> {
516        // This function assumes that all AssignedForeignPoints that have the `is_id`
517        // field set use the same (canonical) value for coordinates x and y.
518        // Otherwise the circuit becomes unsatisfiable, so a malicious prover does not
519        // gain anything from violating this assumption.
520        self.native_gadget.assert_equal(layouter, &p.is_id, &q.is_id)?;
521        self.base_field_chip().assert_equal(layouter, &p.x, &q.x)?;
522        self.base_field_chip().assert_equal(layouter, &p.y, &q.y)
523    }
524
525    fn assert_not_equal(
526        &self,
527        layouter: &mut impl Layouter<F>,
528        p: &AssignedForeignPoint<F, C, B>,
529        q: &AssignedForeignPoint<F, C, B>,
530    ) -> Result<(), Error> {
531        let equal = self.is_equal(layouter, p, q)?;
532        self.native_gadget.assert_equal_to_fixed(layouter, &equal, false)
533    }
534
535    fn assert_equal_to_fixed(
536        &self,
537        layouter: &mut impl Layouter<F>,
538        p: &AssignedForeignPoint<F, C, B>,
539        constant: C::CryptographicGroup,
540    ) -> Result<(), Error> {
541        if constant.is_identity().into() {
542            self.assert_zero(layouter, p)
543        } else {
544            let coordinates = constant.into().coordinates().expect("Valid point");
545            self.base_field_chip().assert_equal_to_fixed(layouter, &p.x, coordinates.0)?;
546            self.base_field_chip().assert_equal_to_fixed(layouter, &p.y, coordinates.1)?;
547            self.assert_non_zero(layouter, p)
548        }
549    }
550
551    fn assert_not_equal_to_fixed(
552        &self,
553        layouter: &mut impl Layouter<F>,
554        p: &AssignedForeignPoint<F, C, B>,
555        constant: C::CryptographicGroup,
556    ) -> Result<(), Error> {
557        if constant.is_identity().into() {
558            self.assert_non_zero(layouter, p)
559        } else {
560            let equal = self.is_equal_to_fixed(layouter, p, constant)?;
561            self.native_gadget.assert_equal_to_fixed(layouter, &equal, false)
562        }
563    }
564}
565
566impl<F, C, B, S, N> EqualityInstructions<F, AssignedForeignPoint<F, C, B>>
567    for ForeignWeierstrassEccChip<F, C, B, S, N>
568where
569    F: CircuitField,
570    C: WeierstrassCurve,
571    B: FieldEmulationParams<F, C::Base>,
572    S: ScalarFieldInstructions<F>,
573    S::Scalar: InnerValue<Element = C::ScalarField>,
574    N: NativeInstructions<F>,
575{
576    fn is_equal(
577        &self,
578        layouter: &mut impl Layouter<F>,
579        p: &AssignedForeignPoint<F, C, B>,
580        q: &AssignedForeignPoint<F, C, B>,
581    ) -> Result<AssignedBit<F>, Error> {
582        // This function needs to return `true` when given two points with the `is_id`
583        // field set, even if their coordinates are different.
584        let eq_coordinates = {
585            let eq_x = self.base_field_chip().is_equal(layouter, &p.x, &q.x)?;
586            let eq_y = self.base_field_chip().is_equal(layouter, &p.y, &q.y)?;
587            let eq_x_and_y = self.native_gadget.and(layouter, &[eq_x, eq_y])?;
588            let both_are_id =
589                self.native_gadget.and(layouter, &[p.is_id.clone(), q.is_id.clone()])?;
590            self.native_gadget.or(layouter, &[eq_x_and_y, both_are_id])?
591        };
592        let eq_id_flag = self.native_gadget.is_equal(layouter, &p.is_id, &q.is_id)?;
593        self.native_gadget.and(layouter, &[eq_id_flag, eq_coordinates])
594    }
595
596    fn is_not_equal(
597        &self,
598        layouter: &mut impl Layouter<F>,
599        x: &AssignedForeignPoint<F, C, B>,
600        y: &AssignedForeignPoint<F, C, B>,
601    ) -> Result<AssignedBit<F>, Error> {
602        let b = self.is_equal(layouter, x, y)?;
603        self.native_gadget.not(layouter, &b)
604    }
605
606    fn is_equal_to_fixed(
607        &self,
608        layouter: &mut impl Layouter<F>,
609        p: &AssignedForeignPoint<F, C, B>,
610        constant: C::CryptographicGroup,
611    ) -> Result<AssignedBit<F>, Error> {
612        if constant.is_identity().into() {
613            Ok(p.is_id.clone())
614        } else {
615            let coordinates = constant.into().coordinates().expect("Valid point");
616            let eq_x = self.base_field_chip().is_equal_to_fixed(layouter, &p.x, coordinates.0)?;
617            let eq_y = self.base_field_chip().is_equal_to_fixed(layouter, &p.y, coordinates.1)?;
618            let p_is_not_id = self.native_gadget.not(layouter, &p.is_id)?;
619            self.native_gadget.and(layouter, &[eq_x, eq_y, p_is_not_id])
620        }
621    }
622
623    fn is_not_equal_to_fixed(
624        &self,
625        layouter: &mut impl Layouter<F>,
626        x: &AssignedForeignPoint<F, C, B>,
627        constant: C::CryptographicGroup,
628    ) -> Result<AssignedBit<F>, Error> {
629        let b = self.is_equal_to_fixed(layouter, x, constant)?;
630        self.native_gadget.not(layouter, &b)
631    }
632}
633
634impl<F, C, B, S, N> ZeroInstructions<F, AssignedForeignPoint<F, C, B>>
635    for ForeignWeierstrassEccChip<F, C, B, S, N>
636where
637    F: CircuitField,
638    C: WeierstrassCurve,
639    B: FieldEmulationParams<F, C::Base>,
640    S: ScalarFieldInstructions<F>,
641    S::Scalar: InnerValue<Element = C::ScalarField>,
642    N: NativeInstructions<F>,
643{
644    fn assert_zero(
645        &self,
646        layouter: &mut impl Layouter<F>,
647        x: &AssignedForeignPoint<F, C, B>,
648    ) -> Result<(), Error> {
649        self.native_gadget.assert_equal_to_fixed(layouter, &x.is_id, true)
650    }
651
652    fn assert_non_zero(
653        &self,
654        layouter: &mut impl Layouter<F>,
655        x: &AssignedForeignPoint<F, C, B>,
656    ) -> Result<(), Error> {
657        self.native_gadget.assert_equal_to_fixed(layouter, &x.is_id, false)
658    }
659
660    fn is_zero(
661        &self,
662        _layouter: &mut impl Layouter<F>,
663        p: &AssignedForeignPoint<F, C, B>,
664    ) -> Result<AssignedBit<F>, Error> {
665        Ok(p.is_id.clone())
666    }
667}
668
669impl<F, C, B, S, N> ControlFlowInstructions<F, AssignedForeignPoint<F, C, B>>
670    for ForeignWeierstrassEccChip<F, C, B, S, N>
671where
672    F: CircuitField,
673    C: WeierstrassCurve,
674    B: FieldEmulationParams<F, C::Base>,
675    S: ScalarFieldInstructions<F>,
676    S::Scalar: InnerValue<Element = C::ScalarField>,
677    N: NativeInstructions<F>,
678{
679    /// Returns `p` if `cond = 1` and `q` otherwise.
680    fn select(
681        &self,
682        layouter: &mut impl Layouter<F>,
683        cond: &AssignedBit<F>,
684        p: &AssignedForeignPoint<F, C, B>,
685        q: &AssignedForeignPoint<F, C, B>,
686    ) -> Result<AssignedForeignPoint<F, C, B>, Error> {
687        let point = p.point.zip(q.point).zip(cond.value()).map(|((p, q), b)| if b { p } else { q });
688        let is_id = self.native_gadget.select(layouter, cond, &p.is_id, &q.is_id)?;
689        let x = self.base_field_chip().select(layouter, cond, &p.x, &q.x)?;
690        let y = self.base_field_chip().select(layouter, cond, &p.y, &q.y)?;
691        Ok(AssignedForeignPoint::<F, C, B> { point, is_id, x, y })
692    }
693}
694
695impl<F, C, B, S, N> EccInstructions<F, C> for ForeignWeierstrassEccChip<F, C, B, S, N>
696where
697    F: CircuitField,
698    C: WeierstrassCurve,
699    B: FieldEmulationParams<F, C::Base>,
700    S: ScalarFieldInstructions<F>,
701    S::Scalar: InnerValue<Element = C::ScalarField>,
702    N: NativeInstructions<F>,
703{
704    type Point = AssignedForeignPoint<F, C, B>;
705    type Coordinate = AssignedField<F, C::Base, B>;
706    type Scalar = S::Scalar;
707
708    fn add(
709        &self,
710        layouter: &mut impl Layouter<F>,
711        p: &Self::Point,
712        q: &Self::Point,
713    ) -> Result<Self::Point, Error> {
714        let r_curve = p.value().zip(q.value()).map(|(p, q)| p + q);
715        let r = self.assign_point_unchecked(layouter, r_curve)?;
716
717        // Define some auxiliary variables.
718        let p_or_q_or_r_are_id = self.native_gadget.or(
719            layouter,
720            &[p.is_id.clone(), q.is_id.clone(), r.is_id.clone()],
721        )?;
722        let none_is_id = self.native_gadget.not(layouter, &p_or_q_or_r_are_id)?;
723        let px_eq_qx = self.base_field_chip().is_equal(layouter, &p.x, &q.x)?;
724        let py_eq_qy = self.base_field_chip().is_equal(layouter, &p.y, &q.y)?;
725        let px_neq_qx = self.native_gadget.not(layouter, &px_eq_qx)?;
726        let py_eq_neg_qy = {
727            let py_plus_qy = self.base_field_chip().add(layouter, &p.y, &q.y)?;
728            self.base_field_chip().is_zero(layouter, &py_plus_qy)?
729        };
730
731        // p = id  =>  r = q.
732        self.cond_assert_equal(layouter, &p.is_id, &r, q)?;
733
734        // q = id  =>  r = p.
735        self.cond_assert_equal(layouter, &q.is_id, &r, p)?;
736
737        // p = -q  =>  r = id.
738        // (The following constraint also encodes <=, which is fine.)
739        let p_eq_nq = self.native_gadget.and(layouter, &[px_eq_qx.clone(), py_eq_neg_qy])?;
740        self.native_gadget.assert_equal(layouter, &p_eq_nq, &r.is_id)?;
741
742        // If p = q (and we are not in an id case), we double.
743        // The following call satisfies the preconditions of [assert_double],
744        // since we have set cond = 0 when p or r are the identity.
745        let cond = self.native_gadget.and(layouter, &[px_eq_qx, py_eq_qy, none_is_id.clone()])?;
746        self.assert_double(layouter, p, &r, &cond)?;
747
748        // If p != q (and we are not in an id case), we enforce the standard
749        // add relation. The following call satisfies the preconditions of
750        // [assert_add], since we have set cond = 0 when p, q or r are the
751        // the identity, or when p.x = q.x.
752        let cond = self.native_gadget.and(layouter, &[px_neq_qx, none_is_id])?;
753        self.assert_add(layouter, p, q, &r, &cond)?;
754
755        Ok(r)
756    }
757
758    fn double(
759        &self,
760        layouter: &mut impl Layouter<F>,
761        p: &AssignedForeignPoint<F, C, B>,
762    ) -> Result<AssignedForeignPoint<F, C, B>, Error> {
763        let r_curve = p.value().map(|p| p + p);
764        let r = self.assign_point_unchecked(layouter, r_curve)?;
765
766        // (There are no points of order 2 by assumption.)
767        self.native_gadget.assert_equal(layouter, &p.is_id, &r.is_id)?;
768
769        // If `p` is not the identity, make sure the double relation is satisfied.
770        // Note that the following call to [assert_double] satisfies the required
771        // preconditions because we set cond := (p != id) and we have asserted that
772        // p = id <=> r = id, so both preconditions of [assert_double] are guaranteed.
773        let cond = self.native_gadget.not(layouter, &p.is_id)?;
774        self.assert_double(layouter, p, &r, &cond)?;
775
776        Ok(r)
777    }
778
779    fn negate(
780        &self,
781        layouter: &mut impl Layouter<F>,
782        p: &Self::Point,
783    ) -> Result<Self::Point, Error> {
784        let neg_y = self.base_field_chip().neg(layouter, &p.y)?;
785        let neg_y = self.base_field_chip().normalize(layouter, &neg_y)?;
786        Ok(AssignedForeignPoint::<F, C, B> {
787            point: -p.point,
788            is_id: p.is_id.clone(),
789            x: p.x.clone(),
790            y: neg_y,
791        })
792    }
793
794    fn msm(
795        &self,
796        layouter: &mut impl Layouter<F>,
797        scalars: &[Self::Scalar],
798        bases: &[Self::Point],
799    ) -> Result<Self::Point, Error> {
800        let scalars = scalars
801            .iter()
802            .map(|s| (s.clone(), C::ScalarField::NUM_BITS as usize))
803            .collect::<Vec<_>>();
804        self.msm_by_bounded_scalars(layouter, &scalars, bases)
805    }
806
807    fn msm_by_bounded_scalars(
808        &self,
809        layouter: &mut impl Layouter<F>,
810        scalars: &[(S::Scalar, usize)],
811        bases: &[AssignedForeignPoint<F, C, B>],
812    ) -> Result<AssignedForeignPoint<F, C, B>, Error> {
813        assert_eq!(scalars.len(), bases.len(), "`|scalars| != |bases|`");
814
815        const WS: usize = 4;
816        let scalar_chip = self.scalar_field_chip();
817
818        let (scalars, bases, bases_with_1bit_scalar) =
819            msm_preprocess(self, scalar_chip, layouter, scalars, bases)?;
820
821        // In order to support the identity point for some bases, we select in-circuit
822        // based on the value of is_id and put a 0 scalar and an arbitrary non-id point
823        // (e.g. the generator) for the base when is_id equals 1.
824        let mut non_id_bases = vec![];
825        let mut scalars_of_non_id_bases = vec![];
826        let zero: S::Scalar = scalar_chip.assign_fixed(layouter, C::ScalarField::ZERO)?;
827        let g = self.assign_fixed(layouter, C::CryptographicGroup::generator())?;
828        for (s, b) in scalars.iter().zip(bases.iter()) {
829            let new_b = self.select(layouter, &b.is_id, &g, b)?;
830            let new_s = scalar_chip.select(layouter, &b.is_id, &zero, &s.0)?;
831            non_id_bases.push(new_b);
832            scalars_of_non_id_bases.push((new_s, s.1));
833        }
834
835        // Scalars with a "bad" bound will be split with GLV into 2 scalars with a
836        // half-size bound.
837        // (The GLV scalars are guaranteed to have half-size.)
838        let nb_bits_per_glv_scalar = C::ScalarField::NUM_BITS.div_ceil(2) as usize;
839        let mut non_glv_scalars = vec![];
840        let mut non_glv_bases = vec![];
841        let mut glv_scalars = vec![];
842        let mut glv_bases = vec![];
843        for (s, b) in scalars_of_non_id_bases.iter().zip(non_id_bases.iter()) {
844            // We heuristically say a bound is "bad" if it far from NUM_BITS / 2 in the
845            // following sense. Note that, ATM, in windowed_msm all sequences
846            // are padded with zeros to meet the longest one.
847            if C::has_cubic_endomorphism() && s.1 > nb_bits_per_glv_scalar + WS {
848                let ((s1, s2), (b1, b2)) = self.glv_split(layouter, &s.0, b)?;
849                glv_scalars.push((s1, nb_bits_per_glv_scalar));
850                glv_scalars.push((s2, nb_bits_per_glv_scalar));
851                glv_bases.push(b1);
852                glv_bases.push(b2);
853            } else {
854                non_glv_scalars.push(s.clone());
855                non_glv_bases.push(b.clone());
856            }
857        }
858
859        let scalars = [glv_scalars, non_glv_scalars].concat();
860        let bases = [glv_bases, non_glv_bases].concat();
861
862        let mut decomposed_scalars = vec![];
863        for (s, nb_bits_s) in scalars.iter() {
864            let s_bits = self.scalar_field_chip().assigned_to_le_chunks(
865                layouter,
866                s,
867                WS,
868                Some(nb_bits_s.div_ceil(WS)),
869            )?;
870            decomposed_scalars.push(s_bits)
871        }
872        let res = self.windowed_msm::<WS>(layouter, &decomposed_scalars, &bases)?;
873
874        add_1bit_scalar_bases(layouter, self, scalar_chip, &bases_with_1bit_scalar, res)
875    }
876
877    fn mul_by_constant(
878        &self,
879        layouter: &mut impl Layouter<F>,
880        scalar: C::ScalarField,
881        base: &Self::Point,
882    ) -> Result<Self::Point, Error> {
883        // We leverage the existing implementation for `mul_by_u128` when the scalar has
884        // 128 bits. Otherwise, we just default to a standard multiplication by an
885        // assigned-fixed scalar.
886        let scalar_as_big = scalar.to_biguint();
887        if scalar_as_big.bits() <= 128 {
888            let n = scalar_as_big
889                .to_u64_digits()
890                .iter()
891                .rev()
892                .fold(0u128, |acc, limb| (acc << 64) | (*limb as u128));
893
894            // `mul_by_u128` is incomplete (it cannot take the identity).
895            // Change the base in case it is the identity and then change
896            // the result back when necessary.
897            let id = self.assign_fixed(layouter, C::CryptographicGroup::identity())?;
898            let g = self.assign_fixed(layouter, C::CryptographicGroup::generator())?;
899            let p = self.select(layouter, &base.is_id, &g, base)?;
900            let r = self.mul_by_u128(layouter, n, &p)?;
901            return self.select(layouter, &base.is_id, &id, &r);
902        }
903        let scalar_bits = scalar
904            .to_bits_le(None)
905            .iter()
906            .map(|b| self.native_gadget.assign_fixed(layouter, *b))
907            .collect::<Result<Vec<_>, Error>>()?;
908        self.msm_by_le_bits(layouter, &[scalar_bits], std::slice::from_ref(base))
909    }
910
911    fn point_from_coordinates(
912        &self,
913        layouter: &mut impl Layouter<F>,
914        x: &AssignedField<F, C::Base, B>,
915        y: &AssignedField<F, C::Base, B>,
916    ) -> Result<Self::Point, Error> {
917        let is_id = self.native_gadget.assign_fixed(layouter, false)?;
918        let cond = self.native_gadget.assign_fixed(layouter, true)?;
919        on_curve::assert_is_on_curve::<F, C, B, N>(
920            layouter,
921            &cond,
922            x,
923            y,
924            self.base_field_chip(),
925            &self.config.on_curve_config,
926        )?;
927        // If from_xy fails, we give the identity as a default value, but note that
928        // the above constraints will make the circuit unsatisfiable.
929        // This is intentional.
930        let point = x
931            .value()
932            .zip(y.value())
933            .map(|(x, y)| C::from_xy(x, y).unwrap_or(C::identity()).into_subgroup());
934        Ok(AssignedForeignPoint::<F, C, B> {
935            point,
936            is_id,
937            x: x.clone(),
938            y: y.clone(),
939        })
940    }
941
942    fn assign_without_subgroup_check(
943        &self,
944        layouter: &mut impl Layouter<F>,
945        value: Value<C::CryptographicGroup>,
946    ) -> Result<Self::Point, Error> {
947        let p = self.assign_point_unchecked(layouter, value)?;
948        let is_not_id = self.native_gadget.not(layouter, &p.is_id)?;
949        on_curve::assert_is_on_curve::<F, C, B, N>(
950            layouter,
951            &is_not_id,
952            &p.x,
953            &p.y,
954            self.base_field_chip(),
955            &self.config.on_curve_config,
956        )?;
957        Ok(p)
958    }
959
960    fn x_coordinate(&self, point: &Self::Point) -> Self::Coordinate {
961        point.x.clone()
962    }
963
964    fn y_coordinate(&self, point: &Self::Point) -> Self::Coordinate {
965        point.y.clone()
966    }
967
968    fn base_field(&self) -> &impl DecompositionInstructions<F, Self::Coordinate> {
969        self.base_field_chip()
970    }
971}
972
973impl<F, C, B, S, N> ForeignWeierstrassEccChip<F, C, B, S, N>
974where
975    F: CircuitField,
976    C: WeierstrassCurve,
977    B: FieldEmulationParams<F, C::Base>,
978    S: ScalarFieldInstructions<F>,
979    S::Scalar: InnerValue<Element = C::ScalarField>,
980    N: NativeInstructions<F>,
981{
982    /// Given config creates new chip that implements foreign ECC
983    /// The RNG is used to sample a random point used for incomplete addition
984    /// Soundness does not rely on this point being random, but completeness
985    /// does.
986    pub fn new(
987        config: &ForeignWeierstrassEccConfig<C>,
988        native_gadget: &N,
989        scalar_field_chip: &S,
990    ) -> Self {
991        let mut rng = OsRng;
992        let random_point = C::random(&mut rng).into_subgroup();
993
994        let base_field_chip = FieldChip::new(&config.base_field_config, native_gadget);
995
996        Self {
997            config: config.clone(),
998            native_gadget: native_gadget.clone(),
999            base_field_chip,
1000            scalar_field_chip: scalar_field_chip.clone(),
1001            tag_cnt: Rc::new(RefCell::new(1)),
1002            msm_randomness: Rc::new(RefCell::new(HashMap::new())),
1003            random_point,
1004        }
1005    }
1006
1007    /// Returns [`Error::CompletenessFailure`] if `value` is known and `f`
1008    /// returns `true`.
1009    fn completeness_error_if<V>(value: &Value<V>, f: impl FnOnce(&V) -> bool) -> Result<(), Error> {
1010        value.error_if_known_and(f).map_err(|_| Error::CompletenessFailure)
1011    }
1012
1013    /// The emulated base field chip of this foreign ECC chip
1014    pub fn base_field_chip(&self) -> &FieldChip<F, C::Base, B, N> {
1015        &self.base_field_chip
1016    }
1017
1018    /// A chip with instructions for the scalar field of this ECC chip.
1019    pub fn scalar_field_chip(&self) -> &S {
1020        &self.scalar_field_chip
1021    }
1022
1023    /// Configures the foreign ECC chip
1024    pub fn configure(
1025        meta: &mut ConstraintSystem<F>,
1026        base_field_config: &FieldChipConfig,
1027        advice_columns: &[Column<Advice>],
1028        nb_parallel_range_checks: usize,
1029        max_bit_len: u32,
1030    ) -> ForeignWeierstrassEccConfig<C> {
1031        // Assert that there is room for the cond_col in the existing columns of the
1032        // field_chip configurations.
1033        let cond_col_idx = base_field_config.x_cols.len() + base_field_config.v_cols.len() + 1;
1034        assert!(advice_columns.len() > cond_col_idx);
1035        let cond_col = advice_columns[cond_col_idx];
1036        meta.enable_equality(cond_col);
1037
1038        let on_curve_config = OnCurveConfig::<C>::configure::<F, B>(
1039            meta,
1040            base_field_config,
1041            &cond_col,
1042            nb_parallel_range_checks,
1043            max_bit_len,
1044        );
1045
1046        let slope_config = SlopeConfig::<C>::configure::<F, B>(
1047            meta,
1048            base_field_config,
1049            &cond_col,
1050            nb_parallel_range_checks,
1051            max_bit_len,
1052        );
1053
1054        let tangent_config = TangentConfig::<C>::configure::<F, B>(
1055            meta,
1056            base_field_config,
1057            &cond_col,
1058            nb_parallel_range_checks,
1059            max_bit_len,
1060        );
1061
1062        let lambda_squared_config = LambdaSquaredConfig::<C>::configure::<F, B>(
1063            meta,
1064            base_field_config,
1065            &cond_col,
1066            nb_parallel_range_checks,
1067            max_bit_len,
1068        );
1069
1070        let (q_multi_select, idx_col_multi_select, tag_col_multi_select) =
1071            configure_multi_select_lookup(meta, advice_columns, base_field_config);
1072
1073        ForeignWeierstrassEccConfig {
1074            base_field_config: base_field_config.clone(),
1075            on_curve_config,
1076            slope_config,
1077            tangent_config,
1078            lambda_squared_config,
1079            q_multi_select,
1080            idx_col_multi_select,
1081            tag_col_multi_select,
1082        }
1083    }
1084
1085    /// Converts a curve point in C : WeierstrassCurve to AssignedForeignPoint.
1086    /// Used for loading possibly secret points into the circuit.
1087    /// The point is not asserted (with constraints) to be on the curve.
1088    /// The point may be the identity.
1089    fn assign_point_unchecked(
1090        &self,
1091        layouter: &mut impl Layouter<F>,
1092        p: Value<C::CryptographicGroup>,
1093    ) -> Result<AssignedForeignPoint<F, C, B>, Error> {
1094        let values = p.map(|p| {
1095            if C::CryptographicGroup::is_identity(&p).into() {
1096                (C::Base::ZERO, C::Base::ZERO, true)
1097            } else {
1098                let coordinates =
1099                    p.into().coordinates().expect("assign_point_unchecked: invalid point given");
1100                (coordinates.0, coordinates.1, false)
1101            }
1102        });
1103        let x = self.base_field_chip().assign(layouter, values.map(|v| v.0))?;
1104        let y = self.base_field_chip().assign(layouter, values.map(|v| v.1))?;
1105        let is_id = self.native_gadget.assign(layouter, values.map(|v| v.2))?;
1106        let p = AssignedForeignPoint::<F, C, B> {
1107            point: p,
1108            is_id,
1109            x,
1110            y,
1111        };
1112        Ok(p)
1113    }
1114
1115    /// Given `p` and `q`, returns `p + q`.
1116    ///
1117    /// This function is incomplete because it is not designed to deal with
1118    /// the cases where `p` or `q` are the identity nor cases where `p = ±q`
1119    /// (i.e. `p.x = q.x`).
1120    /// Consequently, this function will never return the identity point.
1121    ///
1122    /// # Preconditions
1123    ///
1124    /// - `p != id`
1125    /// - `q != id`
1126    /// - `p != q`
1127    ///
1128    /// It is the responsibility of the caller to guarantee that all
1129    /// preconditions are met, this function *does not* necessarily become
1130    /// unsatisfiable if they are violated.
1131    ///
1132    /// # Unsatisfiable Circuit
1133    ///
1134    /// If `p = -q`.
1135    fn incomplete_add(
1136        &self,
1137        layouter: &mut impl Layouter<F>,
1138        p: &AssignedForeignPoint<F, C, B>,
1139        q: &AssignedForeignPoint<F, C, B>,
1140    ) -> Result<AssignedForeignPoint<F, C, B>, Error> {
1141        let r_curve = p.value().zip(q.value()).map(|(p, q)| p + q);
1142        let r = self.assign_point_unchecked(layouter, r_curve)?;
1143
1144        // Assert that r is not the identity.
1145        self.native_gadget.assert_equal(layouter, &p.is_id, &r.is_id)?;
1146
1147        // The preconditions of [incomplete_add] (and the previous assertion
1148        // that r != id) guarantee that the following call satisfies all the
1149        // preconditions of [assert_add].
1150        // Note that the following call to [assert_add] will make the circuit
1151        // unsatisfiable if `p = -q`, but that is the intended behavior of
1152        // [incomplete_add].
1153        let one = self.native_gadget.assign_fixed(layouter, true)?;
1154        self.assert_add(layouter, p, q, &r, &one)?;
1155
1156        Ok(r)
1157    }
1158
1159    /// If `cond = 1`, it asserts that `r = 2p`.
1160    ///
1161    /// If `cond = 0`, it asserts nothing.
1162    ///
1163    /// # Preconditions
1164    ///
1165    ///  - `p != id` or `cond = 0`
1166    ///  - `r != id` or `cond = 0`
1167    ///
1168    /// It is the responsibility of the caller to guarantee that the
1169    /// precondition is met, this function may not become unsatisfiable even if
1170    /// the precondition is violated.
1171    fn assert_double(
1172        &self,
1173        layouter: &mut impl Layouter<F>,
1174        p: &AssignedForeignPoint<F, C, B>,
1175        r: &AssignedForeignPoint<F, C, B>,
1176        cond: &AssignedBit<F>,
1177    ) -> Result<(), Error> {
1178        // λ = 3 * px^2 / (2 * py)
1179        let lambda = {
1180            let lambda_value = p.value().map(|p| {
1181                if C::CryptographicGroup::is_identity(&p).into() {
1182                    C::Base::ONE
1183                } else {
1184                    let p = p.into().coordinates().unwrap();
1185                    (C::Base::from(3) * p.0 * p.0 + C::A)
1186                        * (C::Base::from(2) * p.1).invert().unwrap()
1187                }
1188            });
1189            self.base_field_chip().assign(layouter, lambda_value)?
1190        };
1191
1192        // Assert that λ is the correct slope of the tangent at p.
1193        tangent::assert_tangent::<F, C, B, N>(
1194            layouter,
1195            cond,
1196            (&p.x, &p.y),
1197            &lambda,
1198            self.base_field_chip(),
1199            &self.config.tangent_config,
1200        )?;
1201
1202        // Assert that the value of r.x is correct.
1203        lambda_squared::assert_lambda_squared(
1204            layouter,
1205            cond,
1206            (&p.x, &p.x, &r.x),
1207            &lambda,
1208            self.base_field_chip(),
1209            &self.config.lambda_squared_config,
1210        )?;
1211
1212        // Assert that the slope between p and r is λ (thus r.y is correct).
1213        //
1214        // The preconditions of [assert_double] guarantee that the following call
1215        // satisfies the two first preconditions of [assert_slope].
1216        // The third precondition of [assert_slope], i.e. p.x != r.x, is also
1217        // guaranteed because r.x has been constrained to be correct with
1218        // [assert_lambda_squared], based on a λ that has been constrained to be correct
1219        // with [assert_tangent]. Given our assumption that there do not exist order-3
1220        // points (and our precondition that p != id) we have that 2p != ±p, thus
1221        // r.x != p.x, as desired.
1222        self.assert_slope(layouter, cond, p, r, true, &lambda)?;
1223
1224        Ok(())
1225    }
1226
1227    /// If `cond = 1`, it asserts that `r = p + q`.
1228    ///
1229    /// If `cond = 0`, it asserts nothing.
1230    ///
1231    /// This function is incomplete because it is not designed to deal with
1232    /// the cases where `p` or `q` are the identity nor cases where `p = ±q`
1233    /// (i.e. `p.x = q.x`).
1234    ///
1235    /// # Preconditions
1236    ///
1237    /// - `p != id` or `cond = 0`
1238    /// - `q != id` or `cond = 0`
1239    /// - `r != id` or `cond = 0`
1240    /// - `p != q` or `cond = 0`
1241    ///
1242    /// It is the responsibility of the caller to guarantee that all
1243    /// preconditions are met, this function *does not* necessarily become
1244    /// unsatisfiable if they are violated.
1245    ///
1246    /// # Unsatisfiable Circuit
1247    ///
1248    /// If `p = -q`.
1249    ///
1250    /// # Panics
1251    ///
1252    /// If `p.x = q.x` (the official prover panics, but do not assume this is
1253    /// enforced with constraints.)
1254    fn assert_add(
1255        &self,
1256        layouter: &mut impl Layouter<F>,
1257        p: &AssignedForeignPoint<F, C, B>,
1258        q: &AssignedForeignPoint<F, C, B>,
1259        r: &AssignedForeignPoint<F, C, B>,
1260        cond: &AssignedBit<F>,
1261    ) -> Result<(), Error> {
1262        // λ = (qy - py) / (qx - px)
1263        let lambda = {
1264            let lambda_value = p.value().zip(q.value()).map(|(p, q)| {
1265                if p.is_identity().into() || q.is_identity().into() {
1266                    C::Base::ONE
1267                } else {
1268                    let p = p.into().coordinates().unwrap();
1269                    let q = q.into().coordinates().unwrap();
1270                    if p.0 == q.0 {
1271                        C::Base::ONE
1272                    } else {
1273                        (q.1 - p.1) * (q.0 - p.0).invert().unwrap()
1274                    }
1275                }
1276            });
1277            self.base_field_chip().assign(layouter, lambda_value)?
1278        };
1279
1280        // Assert that λ is the correct slope between p and q.
1281        // The preconditions of [assert_add] guarantee that the following call
1282        // satisfies all the preconditions of [assert_slope]. Indeed, we have:
1283        //  - `p != id` or `cond = 0`
1284        //  - `q != id` or `cond = 0`
1285        //  - `p != q` or `cond = 0`, so precondition (3) of [assert_slope] may be
1286        //    violated, but only with `cond = 1` and `p = -q`, in which case the call to
1287        //    [assert_slope] will make the circuit unsatisfiable. This is exactly the
1288        //    expected behavior of [assert_add].
1289        self.assert_slope(layouter, cond, p, q, false, &lambda)?;
1290
1291        // Assert that the value of r.x is correct.
1292        lambda_squared::assert_lambda_squared(
1293            layouter,
1294            cond,
1295            (&p.x, &q.x, &r.x),
1296            &lambda,
1297            self.base_field_chip(),
1298            &self.config.lambda_squared_config,
1299        )?;
1300
1301        // Assert that the slope between p and -r is λ (thus r.y is correct).
1302        //
1303        // The preconditions of [assert_add] guarantee that the following call
1304        // satisfies the two first preconditions of [assert_slope].
1305        // In general, we will additionally have p.x != r.x, so the third
1306        // precondition would also be satisfied.
1307        //
1308        // Let us carefully analyze the case p.x = r.x.
1309        // Since r.x has been constrained to be correct with [assert_lambda_squared],
1310        // based on a λ that has been constrained to be correct with [assert_slope]
1311        // (between p and q), r.x can be trusted to be the correct value of `(p + q).x`.
1312        // If p.x = r.x we must thus have p + q = ±p, but the + case is not possible
1313        // because q != id. Thus we must have p + q = -p (i.e. r = -p).
1314        // The following call to [assert_slope] would violate the third precondition,
1315        // which means that -r (mind the minus, since argument negate_q is enabled)
1316        // will be constrained to equal p, but this is exactly what we needed.
1317        self.assert_slope(layouter, cond, p, r, true, &lambda)?;
1318
1319        Ok(())
1320    }
1321
1322    /// If `cond = 1`, it asserts that `lambda` is the slope between `p` & `q`:
1323    ///   `lambda = (qy - py) / (qx - px)`.
1324    ///
1325    /// If `cond = 0`, it asserts nothing.
1326    ///
1327    /// If `negate_q` is set to `true`, the check is on the slope between
1328    /// `p` and `-q` instead.
1329    ///
1330    /// # Preconditions
1331    ///
1332    ///   (1) `p != id` or `cond = 0`
1333    ///   (2) `q != id` or `cond = 0`
1334    ///   (3) `p.x != q.x` or `cond = 0`  (non-strict)
1335    ///
1336    /// It is the responsibility of the caller to guarantee that preconditions
1337    /// (1) and (2) are met, this function *does not* become unsatisfiable if
1338    /// they are violated.
1339    ///
1340    /// On the other hand, if precondition (3) is violated, the circuit will
1341    /// become unsatisfiable unless `p = q` (respectively `p = -q` in case
1342    /// `negate_q` is enabled).
1343    fn assert_slope(
1344        &self,
1345        layouter: &mut impl Layouter<F>,
1346        cond: &AssignedBit<F>,
1347        p: &AssignedForeignPoint<F, C, B>,
1348        q: &AssignedForeignPoint<F, C, B>,
1349        negate_q: bool,
1350        lambda: &AssignedField<F, C::Base, B>,
1351    ) -> Result<(), Error> {
1352        slope::assert_slope::<F, C, B, N>(
1353            layouter,
1354            cond,
1355            (&p.x, &p.y),
1356            (&q.x, &q.y, negate_q),
1357            lambda,
1358            self.base_field_chip(),
1359            &self.config.slope_config,
1360        )
1361    }
1362
1363    /// Assigns a region with only 1 row with the limbs of point.x, point.y the
1364    /// given assigned index and the given table_tag.
1365    ///
1366    /// Delegates to [`fill_dynamic_lookup_row`] with this chip's columns.
1367    #[allow(clippy::type_complexity)]
1368    fn fill_dynamic_lookup_row(
1369        &self,
1370        layouter: &mut impl Layouter<F>,
1371        point: &AssignedForeignPoint<F, C, B>,
1372        index: &AssignedNative<F>,
1373        table_tag: F,
1374        enable_lookup: bool,
1375    ) -> Result<(Vec<AssignedNative<F>>, Vec<AssignedNative<F>>), Error> {
1376        fill_dynamic_lookup_row(
1377            layouter,
1378            &point.x.limb_values(),
1379            &point.y.limb_values(),
1380            index,
1381            &self.config.base_field_config.x_cols,
1382            &self.config.base_field_config.z_cols, // z_cols used for y (y_cols == x_cols)
1383            self.config.idx_col_multi_select,
1384            self.config.tag_col_multi_select,
1385            self.config.q_multi_select,
1386            table_tag,
1387            enable_lookup,
1388        )
1389    }
1390
1391    /// Prepares a lookup table for then applying multi_select.
1392    /// The i-th assigned point in `point_table` (starting from i = 0) will be
1393    /// paired with index selector i and the given table tag (a separator
1394    /// between different tables).
1395    ///
1396    /// # Precondition
1397    ///
1398    /// We require that all points in the table be different from the identity.
1399    /// Furthermore, the coordinates of all the points in the tables must have
1400    /// well-formed bounds.
1401    /// It is the responsibility of the caller to make sure this is satisfied.
1402    fn load_multi_select_table(
1403        &self,
1404        layouter: &mut impl Layouter<F>,
1405        point_table: &[AssignedForeignPoint<F, C, B>],
1406        table_tag: F,
1407    ) -> Result<(), Error> {
1408        for (i, point) in point_table.iter().enumerate() {
1409            let index = self.native_gadget.assign_fixed(layouter, F::from(i as u64))?;
1410            self.fill_dynamic_lookup_row(layouter, point, &index, table_tag, false)?;
1411        }
1412        Ok(())
1413    }
1414
1415    /// Returns the i-th point in `point_table` where `i` is the value contained
1416    /// in `selector`.
1417    ///
1418    /// # Precondition
1419    ///
1420    /// We require that all points in the table be different from the identity.
1421    /// Furthermore, the coordinates of all the points in the tables must have
1422    /// well-formed bounds. Finally, we require that the table has been loaded
1423    /// with indices in the range [0, |point_table|), see
1424    /// `load_multi_select_table`.
1425    ///
1426    /// It is the responsibility of the caller to make sure this is satisfied.
1427    ///
1428    /// # Unsatisfiable Circuit
1429    ///
1430    /// If the preconditions are met but the value of `selector` is not in the
1431    /// range [0, |point_table|).
1432    ///
1433    /// # Panics
1434    ///
1435    /// In debug mode, if |point_table| exceeds 2^32 or the value of `selector`
1436    /// is not in the range [0, |point_table|).
1437    fn multi_select(
1438        &self,
1439        layouter: &mut impl Layouter<F>,
1440        selector: &AssignedNative<F>,
1441        point_table: &[AssignedForeignPoint<F, C, B>],
1442        table_tag: F,
1443    ) -> Result<AssignedForeignPoint<F, C, B>, Error> {
1444        // This is a hack, but it's good enough for now.
1445        let mut selector_idx = 0;
1446        selector.value().map(|v| {
1447            let digits = v.to_biguint().to_u32_digits();
1448            let digit = if digits.is_empty() { 0 } else { digits[0] };
1449            debug_assert!(digits.len() <= 1);
1450            debug_assert!(digit < point_table.len() as u32);
1451            selector_idx = digit;
1452        });
1453
1454        let selected = point_table[selector_idx as usize].clone();
1455
1456        // Make sure that the limbs of `selected` appear in the lookup for this table.
1457        // Only one point can appear next to `selector`, this ensures `selected` is
1458        // correct.
1459        let (xs, ys) =
1460            self.fill_dynamic_lookup_row(layouter, &selected, selector, table_tag, true)?;
1461        let x = AssignedField::<F, C::Base, B>::from_limbs_unsafe(xs);
1462        let y = AssignedField::<F, C::Base, B>::from_limbs_unsafe(ys);
1463        let is_id = self.native_gadget.assign_fixed(layouter, false)?;
1464
1465        let result = AssignedForeignPoint::<F, C, B> {
1466            point: selected.point,
1467            is_id,
1468            x,
1469            y,
1470        };
1471
1472        Ok(result)
1473    }
1474
1475    /// Given a table of `n` assigned points and `k` unassigned points
1476    /// presumably on the table, it returns the `k` points assigned (in the
1477    /// same order) and introduces constraints that guarantee that:
1478    ///   1. All the `k` assigned points are on the table.
1479    ///   2. All the `k` assigned points correspond to different table entries
1480    ///      (they are pair-wise different if the table has no duplicates).
1481    ///
1482    /// # Precondition
1483    ///
1484    /// The `selected` points must be provided in order of occurrence in the
1485    /// table, otherwise a Synthesis error will be triggered.
1486    ///
1487    /// The `table` points cannot be the identity, otherwise the circuit will
1488    /// become unsatisfiable.
1489    //
1490    // This is implemented through dynamic lookups and with a careful design so that
1491    // the number of constraints is linear in `n` (and not quadratic).
1492    pub fn k_out_of_n_points(
1493        &self,
1494        layouter: &mut impl Layouter<F>,
1495        table: &[AssignedForeignPoint<F, C, B>],
1496        selected: &[Value<C::CryptographicGroup>],
1497    ) -> Result<Vec<AssignedForeignPoint<F, C, B>>, Error> {
1498        let n = table.len();
1499        let k = selected.len();
1500        assert!(k <= n);
1501
1502        // Just to make sure, although we would have RAM issues otherwise.
1503        assert!((n as u128) < (1 << (F::NUM_BITS / 2)));
1504
1505        // Assert that the table points are not the identity.
1506        table.iter().try_for_each(|point| self.assert_non_zero(layouter, point))?;
1507
1508        // Load the table with a fresh tag, and increase the tag for the next use.
1509        // This associates index i from 0 to k-1 to the i-th table entry.
1510        //
1511        // TODO: Having an independent function for loading the table of points would
1512        // allow us to share the table if this function is invoked several times on
1513        // a common table.
1514        let tag_cnt = *self.tag_cnt.borrow();
1515        self.tag_cnt.replace(tag_cnt + 1);
1516        self.load_multi_select_table(layouter, table, F::from(tag_cnt))?;
1517
1518        // Find the index for each of the `k` selected points.
1519        let table_values =
1520            Value::<Vec<C::CryptographicGroup>>::from_iter(table.iter().map(|point| point.value()));
1521        let selected_idxs = selected
1522            .iter()
1523            .map(|point_value| {
1524                point_value
1525                    .zip(table_values.clone())
1526                    .map(|(p, ts)| ts.iter().position(|table_val| *table_val == p).unwrap_or(0))
1527            })
1528            .collect::<Vec<_>>();
1529
1530        // Assert that the selected values were provided in order of occurrence, this is
1531        // just a sanity check on CPU.
1532        Value::<Vec<usize>>::from_iter(selected_idxs.clone())
1533            .error_if_known_and(|idxs| idxs.iter().zip(idxs.iter().skip(1)).any(|(i, j)| i >= j))?;
1534
1535        // Witness the selected indices.
1536        let assigned_selected_idxs = selected_idxs
1537            .clone()
1538            .iter()
1539            .map(|i_value| self.native_gadget.assign(layouter, i_value.map(|i| F::from(i as u64))))
1540            .collect::<Result<Vec<AssignedNative<F>>, Error>>()?;
1541
1542        // Introduce constraints that guarantee that all the assigned indices are
1543        // different. For this, we will compute the delta between indices and enforce
1544        // that they are all in the range [1, l] where l is a small bound, greater than
1545        // or equal to `n` for completeness, that prevents wrap-arounds.
1546        // Choosing `l` as a power of 2 will make range-checks slightly more efficient.
1547        let l = BigUint::one() << BigUint::from(n).bits();
1548        assigned_selected_idxs
1549            .iter()
1550            .zip(assigned_selected_idxs.iter().skip(1))
1551            .try_for_each(|(idx, next_idx)| {
1552                let diff_minus_one = self.native_gadget.linear_combination(
1553                    layouter,
1554                    &[(F::ONE, next_idx.clone()), (-F::ONE, idx.clone())],
1555                    -F::ONE,
1556                )?;
1557                self.native_gadget.assert_lower_than_fixed(layouter, &diff_minus_one, &l)
1558            })?;
1559
1560        // Witness the selected points while asserting they are on the table.
1561        let mut unwrapped_selected_idxs = vec![0; k];
1562        selected_idxs.iter().enumerate().for_each(|(i, idx)| {
1563            idx.map(|j| unwrapped_selected_idxs[i] = j);
1564        });
1565        let selected_points = unwrapped_selected_idxs
1566            .iter()
1567            .zip(assigned_selected_idxs.iter())
1568            .map(|(i, selected_idx)| {
1569                let (xs, ys) = self.fill_dynamic_lookup_row(
1570                    layouter,
1571                    &table[*i],
1572                    selected_idx,
1573                    F::from(tag_cnt),
1574                    true,
1575                )?;
1576                let x = AssignedField::<F, C::Base, B>::from_limbs_unsafe(xs);
1577                let y = AssignedField::<F, C::Base, B>::from_limbs_unsafe(ys);
1578                let is_id = self.native_gadget.assign_fixed(layouter, false)?;
1579                Ok(AssignedForeignPoint::<F, C, B> {
1580                    point: table[*i].value(),
1581                    is_id,
1582                    x,
1583                    y,
1584                })
1585            })
1586            .collect::<Result<Vec<_>, Error>>()?;
1587
1588        Ok(selected_points)
1589    }
1590
1591    /// Assert that the given two points have a different x-coordinate.
1592    ///
1593    /// WARNING: This function is sound but not complete.
1594    /// Concretely, if p.x and q.x are different, but when interpreted as
1595    /// integers they are equal modulo the native modulus, this assertion will
1596    /// fail.
1597    fn incomplete_assert_different_x(
1598        &self,
1599        layouter: &mut impl Layouter<F>,
1600        p: &AssignedForeignPoint<F, C, B>,
1601        q: &AssignedForeignPoint<F, C, B>,
1602    ) -> Result<(), Error> {
1603        assert!(p.x.is_well_formed());
1604        assert!(q.x.is_well_formed());
1605
1606        // Modular integers have at most two representations in limbs form,
1607        // because m <= B^(nb_limbs) < 2m, where m is the emulated modulus and B is
1608        // the base of representation.
1609        // In other words, an integer x may be represented with limbs x_i such that
1610        // sum_x = x or sum_x = x + m, where sum_x := 1 + sum_i B^i x_i.
1611        //
1612        // In order to check that x and y are different modulo m, it is enough to check
1613        // that (sum_x - sum_y) does not fall in one of the values {0, m, -m}.
1614        //
1615        // We will enforce this check modulo the native modulus only. This is
1616        // incomplete, but sound.
1617
1618        let native_gadget = &self.native_gadget;
1619        let base = big_to_fe::<F>(BigUint::one() << B::LOG2_BASE);
1620        let m = bigint_to_fe::<F>(&p.x.modulus());
1621
1622        let mut terms = vec![];
1623        let mut coeff = F::ONE;
1624        for (px_i, qx_i) in p.x.limb_values().iter().zip(q.x.limb_values().iter()) {
1625            terms.push((coeff, px_i.clone()));
1626            terms.push((-coeff, qx_i.clone()));
1627            coeff *= base;
1628        }
1629
1630        let diff = native_gadget.linear_combination(layouter, &terms, F::ZERO)?;
1631
1632        // We assert that `diff not in {0, m, -m}`.
1633        // TODO: the following could be done more efficiently if we had dedicated
1634        // instructions in the native gadget.
1635        native_gadget.assert_non_zero(layouter, &diff)?;
1636        native_gadget.assert_not_equal_to_fixed(layouter, &diff, m)?;
1637        native_gadget.assert_not_equal_to_fixed(layouter, &diff, -m)
1638    }
1639
1640    /// Returns `n * p`, where `n` is a constant `u128`.
1641    ///
1642    /// # Precondition
1643    ///
1644    /// - `p != id`
1645    ///
1646    /// It is the responsibility of the caller to meet the precondition.
1647    /// This function neither panics nor makes the circuit unsatisfiable if the
1648    /// precondition is violated.
1649    fn mul_by_u128(
1650        &self,
1651        layouter: &mut impl Layouter<F>,
1652        n: u128,
1653        p: &AssignedForeignPoint<F, C, B>,
1654    ) -> Result<AssignedForeignPoint<F, C, B>, Error> {
1655        if n == 0 {
1656            return self.assign_fixed(layouter, C::CryptographicGroup::identity());
1657        };
1658
1659        // Assert that (n : u128) is smaller than ORDER / 2.
1660        // This condition allows us to use incomplete addition in the loop below.
1661        assert!(129 < C::ScalarField::NUM_BITS);
1662
1663        // Double-and-add (starting from the LSB)
1664
1665        let mut res = None;
1666
1667        // tmp will encode (2^i p) on every iteration, to be added (selectively) to res.
1668        let mut tmp = p.clone();
1669
1670        // We iterate over the bits of n.
1671        let mut n = n;
1672        while n > 0 {
1673            // In order to safetly use [incomplete_add] here, we need to show
1674            // that these three preconditions are met when res != None:
1675            //   (1) res != id
1676            //   (2) tmp != id
1677            //   (3) res.x != tmp.x   (i.e. res != ±tmp)
1678            //
1679            // Note that p != id holds by assumption.
1680            // Also, note that on the i-th iteration (counting from i = 0),
1681            // at this point we have:
1682            //    res := None or Some(k p) with 0 < k < 2^i
1683            //    tmp := 2^i p
1684            //
1685            // (1) & (2) hold because all non-identity points have Scalar.ORDER order by
1686            //           assumption, and 2^i is smaller than ORDER on every iteration.
1687            // (3) holds because otherwise we would have k p = ± 2^i p with 0 < k < 2^i.
1688            //     Thus (k ∓ 2^i) p = id, which means that (k ∓ 2^i) is
1689            //     a multiple of ORDER, however this is not possible as:
1690            //        (i) k ∓ 2^i != 0 because 0 < k < 2^i
1691            //       (ii) k - 2^i > -2^i > -ORDER    (because i < 128 < |ORDER|)
1692            //      (iii) k + 2^i < 2^(i+1) < ORDER  (because i+1 < 129 < |ORDER|)
1693            if !n.is_multiple_of(2) {
1694                res = match res {
1695                    None => Some(tmp.clone()),
1696                    Some(acc) => Some(self.incomplete_add(layouter, &acc, &tmp)?),
1697                };
1698            }
1699            n >>= 1;
1700
1701            if n > 0 {
1702                tmp = self.double(layouter, &tmp)?
1703            }
1704        }
1705
1706        Ok(res.unwrap())
1707    }
1708
1709    /// Returns the shared MSM randomness for window size `WS`, initializing
1710    /// it on first call for that window size.
1711    ///
1712    /// On first call for a given `WS`, samples a random curve point `r` and
1713    /// computes `α = (2^WS - 1) * r` and `neg_alpha = -α`. The point `r` is
1714    /// constrained to not be the identity.
1715    ///
1716    /// On subsequent calls with the same `WS`, returns the cached randomness.
1717    /// Different window sizes get independent randomness.
1718    ///
1719    /// # Postconditions
1720    ///
1721    /// - `r` is not the identity point (enforced in-circuit).
1722    /// - `neg_alpha = -((2^WS - 1) * r)`.
1723    fn msm_randomness<const WS: usize>(
1724        &self,
1725        layouter: &mut impl Layouter<F>,
1726    ) -> Result<MsmRandomness<F, C, B>, Error> {
1727        if let Some(cached) = self.msm_randomness.borrow().get(&WS) {
1728            return Ok(cached.clone());
1729        }
1730
1731        // Reuse `r` from any cached window size to avoid unnecessary point assignments.
1732        let r = match self.msm_randomness.borrow().values().next().map(|c| c.r.clone()) {
1733            Some(r) => r,
1734            None => {
1735                self.assign_without_subgroup_check(layouter, Value::known(self.random_point))?
1736            }
1737        };
1738        self.assert_non_zero(layouter, &r)?;
1739
1740        let alpha = self.mul_by_u128(layouter, (1u128 << WS) - 1, &r)?;
1741        let neg_alpha = self.negate(layouter, &alpha)?;
1742
1743        let windowed_randomness = MsmRandomness { r, neg_alpha };
1744        self.msm_randomness.borrow_mut().insert(WS, windowed_randomness.clone());
1745        Ok(windowed_randomness)
1746    }
1747
1748    /// Curve multi-scalar multiplication.
1749    /// This implementation uses a windowed double-and-add with `WS` window
1750    /// size.
1751    /// The scalars are represented by little-endian sequences of chunks in the
1752    /// range [0, 2^WS), represented by an AssignedNative value.
1753    ///
1754    /// # Preconditions
1755    ///
1756    /// (1) `scalars[i][j] in [0, 2^WS)` for every `i, j`.
1757    /// (2) `base_i != identity` for every `i`
1758    ///
1759    /// It is the responsibility of the caller to meet precondition (1).
1760    ///
1761    /// # Unsatisfiable Circuit
1762    ///
1763    /// If precondition (2) is violated.
1764    ///
1765    /// # Panics
1766    ///
1767    /// If `|scalars| != |bases|`.
1768    fn windowed_msm<const WS: usize>(
1769        &self,
1770        layouter: &mut impl Layouter<F>,
1771        scalars: &[Vec<AssignedNative<F>>],
1772        bases: &[AssignedForeignPoint<F, C, B>],
1773    ) -> Result<AssignedForeignPoint<F, C, B>, Error> {
1774        assert_eq!(scalars.len(), bases.len(), "`|scalars| != |bases|`");
1775
1776        if scalars.is_empty() {
1777            return self.assign_fixed(layouter, C::CryptographicGroup::identity());
1778        }
1779
1780        // Assert that none of the bases is the identity point
1781        for p in bases.iter() {
1782            self.assert_non_zero(layouter, p)?;
1783        }
1784
1785        // Pad all the sequences of chunks to have the same length.
1786        // TODO: This is not strictly necessary, we could be more efficient if some
1787        // sequences are shorter. For now we do not do this, as it is highly involved,
1788        // and not very compatible with our random accumulation trick below.
1789        let zero: AssignedNative<F> = self.native_gadget.assign_fixed(layouter, F::ZERO)?;
1790        let max_len = scalars.iter().fold(0, |m, chunks| max(m, chunks.len()));
1791        let mut padded_scalars = vec![];
1792        for s_bits in scalars.iter() {
1793            // Pad with zeros up to length `padded_len`.
1794            let mut s_bits = s_bits.to_vec();
1795            s_bits.resize(max_len, zero.clone());
1796            // Reverse to get big-endian order, for the double-and-add.
1797            let rev_s_bits = s_bits.into_iter().rev().collect::<Vec<_>>();
1798            padded_scalars.push(rev_s_bits)
1799        }
1800
1801        // Sample `r`, a random point that allows us to use incomplete addition in the
1802        // double-and-add loop.
1803        // The value of `r` can be chosen by the prover (who will choose it uniformly
1804        // at random for statistical completeness) and this is not a problem for
1805        // soundness.
1806        //
1807        // The randomness `r` is shared across all MSM calls on this chip, so that
1808        // precomputed tables (which depend on α = (2^WS - 1) * r) can be cached
1809        // and reused for the same base points.
1810        //
1811        // Let l := bases.len(), the initial double-and-add accumulator will be
1812        // acc := l * r. On every double-and-add iteration i, we will double WS times
1813        // and then, for every j in [l], conditionally add (kj_i * base_j - α)
1814        // or (-α), depending on the relevant segment, kj_i, of scalars_j at
1815        // that iteration, where α := (2^WS - 1) r. After this, the total randomness in
1816        // the accumulator becomes:
1817        //   2^WS (l * r) - l * α = 2^WS (l * r) - l * (2^WS - 1) r = l * r.
1818        // This makes the total randomness invariant (l * r) at the beginning of every
1819        // iteration.
1820        //
1821        // To finish the computation we will thus need to subtract l * r, which will
1822        // be done in-circuit.
1823        //
1824        // TODO: Maybe we should check that the sampled r will not have a completeness
1825        // problem. The probability should be overwhelming, but if the bad event
1826        // happened, the proof would fail. We could sample another r here instead.
1827        let MsmRandomness { r, neg_alpha } = self.msm_randomness::<WS>(layouter)?;
1828
1829        let l_times_r = self.mul_by_u128(layouter, bases.len() as u128, &r)?;
1830
1831        // Get the global tag counter and increase it with |bases|
1832        let tag_cnt = *self.tag_cnt.clone().borrow();
1833        self.tag_cnt.replace(tag_cnt + bases.len() as u64);
1834
1835        // Compute table, [-α, p-α, 2p-α, ..., (2^WS-1)p-α] for every p in bases.
1836        let mut tables = vec![];
1837        for (i, p) in bases.iter().enumerate() {
1838            // Assert that α.x ≠ p.x (note that α and -α have the same x-coordinate).
1839            self.incomplete_assert_different_x(layouter, &neg_alpha, p)?;
1840            let mut acc = neg_alpha.clone();
1841            let mut p_table = vec![acc.clone()];
1842            for _ in 1..(1usize << WS) {
1843                // In order to safetly use [incomplete_add] here, we need to ensure that:
1844                //   (1) acc != id
1845                //   (2) p != id
1846                //   (3) acc != p
1847                //
1848                // (1) holds because acc is initially -α, which cannot be the identity,
1849                //     because r is not and we have asserted 2^WS < Scalar::ORDER. Then,
1850                //     acc is always the result of incomplete_add, which is guaranteed to
1851                //     not produce the identity.
1852                // (2) holds because all the bases were asserted to not be the identity.
1853                // (3) holds because acc is initially -α, which has been asserted to not
1854                //     share the x coordinate with p, so the first iteration is fine.
1855                //     In other iterations, acc will be of the form kp-α for some
1856                //     k = 1,...,(2^WS-2). Note that (k-1)p-α cannot be the identity as it is
1857                //     the result of a previous call to [incomplete_add], thus kp-α != p, so
1858                //     the third precondition of [incomplete_add] is met.
1859                Self::completeness_error_if(&acc.value().zip(p.value()), |(av, pv)| {
1860                    av == pv || *av == -(*pv)
1861                })?;
1862
1863                acc = self.incomplete_add(layouter, &acc, p)?;
1864
1865                assert!(acc.x.is_well_formed() && acc.y.is_well_formed());
1866                p_table.push(acc.clone())
1867            }
1868            self.load_multi_select_table(layouter, &p_table, F::from(tag_cnt + i as u64))?;
1869            tables.push(p_table)
1870        }
1871
1872        let nb_iterations = max_len;
1873        let mut acc = l_times_r.clone();
1874
1875        #[allow(clippy::needless_range_loop)]
1876        for i in 0..nb_iterations {
1877            for _ in 0..WS {
1878                acc = self.double(layouter, &acc)?;
1879            }
1880            for j in 0..bases.len() {
1881                let window = &padded_scalars[j][i];
1882                let addend =
1883                    self.multi_select(layouter, window, &tables[j], F::from(tag_cnt + j as u64))?;
1884                // In order to safetly use [incomplete_add] here, we need to ensure that:
1885                //   (1) acc != id
1886                //   (2) addend != id
1887                //   (3) acc != addend
1888                //
1889                // (1) holds because acc is the result of doubling a non-identity point
1890                //     (this is guaranteed because in the first iteration acc != id, and in
1891                //     any other iteration acc is the result of incomplete_add, which is
1892                //     guaranteed to not produce the identity.)
1893                // (2) holds because all the points in the tables are different from the
1894                //     identity, as asserted above (in the construction of the tables).
1895                // (3) is asserted here, this assertion will not hinder completeness except
1896                //     with negligible probability (over the choice of α).
1897                Self::completeness_error_if(&acc.value().zip(addend.value()), |(av, addv)| {
1898                    av == addv || *av == -(*addv)
1899                })?;
1900
1901                self.incomplete_assert_different_x(layouter, &acc, &addend)?;
1902                acc = self.incomplete_add(layouter, &acc, &addend)?;
1903            }
1904        }
1905
1906        let r_correction = self.negate(layouter, &l_times_r)?;
1907        self.add(layouter, &acc, &r_correction)
1908    }
1909
1910    /// Same as [self.msm], but the scalars are represented as little-endian
1911    /// sequences of bits. The length of the bit sequences can be arbitrary
1912    /// and possibly distinct between terms.
1913    ///
1914    /// # Precondition
1915    ///
1916    /// `base_i != identity` for every `i`
1917    ///
1918    /// # Unsatisfiable Circuit
1919    ///
1920    /// If the precondition is violated.
1921    ///
1922    /// # Panics
1923    ///
1924    /// If `|scalars| != |bases|`.
1925    pub fn msm_by_le_bits(
1926        &self,
1927        layouter: &mut impl Layouter<F>,
1928        scalars: &[Vec<AssignedBit<F>>],
1929        bases: &[AssignedForeignPoint<F, C, B>],
1930    ) -> Result<AssignedForeignPoint<F, C, B>, Error> {
1931        // Windows of size 4 seem to be optimal for 256-bit scalar fields,
1932        // because k = 4 minimizes 2^k + 256 / k.
1933        // TODO: Pick window size based on C::ScalarField::NUM_BITS?
1934        const WS: usize = 4;
1935        let scalars = scalars
1936            .iter()
1937            .map(|bits| {
1938                bits.chunks(WS)
1939                    .map(|chunk| self.native_gadget.assigned_from_le_bits(layouter, chunk))
1940                    .collect::<Result<Vec<_>, Error>>()
1941            })
1942            .collect::<Result<Vec<_>, Error>>()?;
1943        self.windowed_msm::<WS>(layouter, &scalars, bases)
1944    }
1945
1946    /// Takes a (potentially full-size) assigned scalar `x` and an assigned
1947    /// point `P` and returns 2 assigned scalars `(x1, x2)` and 2 assigned
1948    /// points `(P1, P2)` that are guaranteed (with circuit constraints) to
1949    /// satisfy `x P = x1 P1 + x2 P2`.
1950    ///
1951    /// The returned scalars `(x1, x2)` are half-size, although this is not
1952    /// enforced with constraints here, so they can be decomposed into
1953    /// C::ScalarField::NUM_BITS / 2 bits without completeness errors.
1954    #[allow(clippy::type_complexity)]
1955    fn glv_split(
1956        &self,
1957        layouter: &mut impl Layouter<F>,
1958        scalar: &S::Scalar,
1959        base: &AssignedForeignPoint<F, C, B>,
1960    ) -> Result<
1961        (
1962            (S::Scalar, S::Scalar),
1963            (AssignedForeignPoint<F, C, B>, AssignedForeignPoint<F, C, B>),
1964        ),
1965        Error,
1966    > {
1967        let zeta_base = C::base_zeta();
1968        let zeta_scalar = C::scalar_zeta();
1969
1970        let decomposed = scalar.value().map(|x| glv_scalar_decomposition(&x, &zeta_scalar));
1971        let s1_value = decomposed.map(|((s1, _), _)| s1);
1972        let x1_value = decomposed.map(|((_, x1), _)| x1);
1973        let s2_value = decomposed.map(|(_, (s2, _))| s2);
1974        let x2_value = decomposed.map(|(_, (_, x2))| x2);
1975
1976        let x1 = self.scalar_field_chip.assign(layouter, x1_value)?;
1977        let x2 = self.scalar_field_chip.assign(layouter, x2_value)?;
1978
1979        let s1 = self.native_gadget.assign(layouter, s1_value)?;
1980        let s2 = self.native_gadget.assign(layouter, s2_value)?;
1981
1982        let neg_x1 = self.scalar_field_chip.neg(layouter, &x1)?;
1983        let neg_x2 = self.scalar_field_chip.neg(layouter, &x2)?;
1984
1985        let signed_x1 = self.scalar_field_chip.select(layouter, &s1, &x1, &neg_x1)?;
1986        let signed_x2 = self.scalar_field_chip.select(layouter, &s2, &x2, &neg_x2)?;
1987
1988        // Assert that scalar = signed_x1 + zeta * signed_x2
1989        let x = self.scalar_field_chip.linear_combination(
1990            layouter,
1991            &[(C::ScalarField::ONE, signed_x1), (zeta_scalar, signed_x2)],
1992            C::ScalarField::ZERO,
1993        )?;
1994        self.scalar_field_chip.assert_equal(layouter, &x, scalar)?;
1995
1996        let zeta_x = self.base_field_chip.mul_by_constant(layouter, &base.x, zeta_base)?;
1997        let zeta_p = AssignedForeignPoint::<F, C, B> {
1998            point: base.point.map(|p| {
1999                if p.is_identity().into() {
2000                    p
2001                } else {
2002                    let coordinates = p.into().coordinates().unwrap();
2003                    let zeta_x = zeta_base * coordinates.0;
2004                    C::from_xy(zeta_x, coordinates.1).unwrap().into_subgroup()
2005                }
2006            }),
2007            is_id: base.is_id.clone(),
2008            x: zeta_x,
2009            y: base.y.clone(),
2010        };
2011
2012        let neg_zeta_p = self.negate(layouter, &zeta_p)?;
2013        let neg_base = self.negate(layouter, base)?;
2014
2015        let p1 = self.select(layouter, &s1, base, &neg_base)?;
2016        let p2 = self.select(layouter, &s2, &zeta_p, &neg_zeta_p)?;
2017
2018        Ok(((x1, x2), (p1, p2)))
2019    }
2020}
2021
2022#[derive(Clone, Debug)]
2023#[cfg(any(test, feature = "testing"))]
2024/// Configuration used to implement `FromScratch` for the ForeignEcc chip. This
2025/// should only be used for testing.
2026pub struct ForeignEccTestConfig<F, C, S, N>
2027where
2028    F: CircuitField,
2029    C: WeierstrassCurve,
2030    S: ScalarFieldInstructions<F> + FromScratch<F>,
2031    S::Scalar: InnerValue<Element = C::ScalarField>,
2032    N: NativeInstructions<F> + FromScratch<F>,
2033{
2034    native_gadget_config: <N as FromScratch<F>>::Config,
2035    scalar_field_config: <S as FromScratch<F>>::Config,
2036    ff_ecc_config: ForeignWeierstrassEccConfig<C>,
2037}
2038
2039#[cfg(any(test, feature = "testing"))]
2040impl<F, C, B, S, N> FromScratch<F> for ForeignWeierstrassEccChip<F, C, B, S, N>
2041where
2042    F: CircuitField,
2043    C: WeierstrassCurve,
2044    B: FieldEmulationParams<F, C::Base>,
2045    S: ScalarFieldInstructions<F> + FromScratch<F>,
2046    S::Scalar: InnerValue<Element = C::ScalarField>,
2047    N: NativeInstructions<F> + FromScratch<F>,
2048{
2049    type Config = ForeignEccTestConfig<F, C, S, N>;
2050
2051    fn new_from_scratch(config: &ForeignEccTestConfig<F, C, S, N>) -> Self {
2052        let native_gadget = <N as FromScratch<F>>::new_from_scratch(&config.native_gadget_config);
2053        let scalar_field_chip =
2054            <S as FromScratch<F>>::new_from_scratch(&config.scalar_field_config);
2055        ForeignWeierstrassEccChip::new(&config.ff_ecc_config, &native_gadget, &scalar_field_chip)
2056    }
2057
2058    fn load_from_scratch(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
2059        self.native_gadget.load_from_scratch(layouter)?;
2060        self.scalar_field_chip.load_from_scratch(layouter)
2061    }
2062
2063    fn configure_from_scratch(
2064        meta: &mut ConstraintSystem<F>,
2065        advice_columns: &mut Vec<Column<Advice>>,
2066        fixed_columns: &mut Vec<Column<Fixed>>,
2067        instance_columns: &[Column<Instance>; 2],
2068    ) -> ForeignEccTestConfig<F, C, S, N> {
2069        let native_gadget_config = <N as FromScratch<F>>::configure_from_scratch(
2070            meta,
2071            advice_columns,
2072            fixed_columns,
2073            instance_columns,
2074        );
2075        let scalar_field_config = <S as FromScratch<F>>::configure_from_scratch(
2076            meta,
2077            advice_columns,
2078            fixed_columns,
2079            instance_columns,
2080        );
2081        let nb_advice_cols = nb_foreign_ecc_chip_columns::<F, C, B, S>();
2082        while advice_columns.len() < nb_advice_cols {
2083            advice_columns.push(meta.advice_column());
2084        }
2085        // Use hard-coded pow2range values matching NativeGadget::configure_from_scratch
2086        let nb_parallel_range_checks = 4;
2087        let max_bit_len = 8;
2088        let base_field_config = FieldChip::<F, C::Base, B, N>::configure(
2089            meta,
2090            &advice_columns[..nb_advice_cols],
2091            nb_parallel_range_checks,
2092            max_bit_len,
2093        );
2094        let ff_ecc_config = ForeignWeierstrassEccChip::<F, C, B, S, N>::configure(
2095            meta,
2096            &base_field_config,
2097            &advice_columns[..nb_advice_cols],
2098            nb_parallel_range_checks,
2099            max_bit_len,
2100        );
2101        ForeignEccTestConfig {
2102            native_gadget_config,
2103            scalar_field_config,
2104            ff_ecc_config,
2105        }
2106    }
2107}
2108
2109#[cfg(test)]
2110mod tests {
2111    use group::Group;
2112    use midnight_curves::{k256::K256, p256::P256, Fq as BlsScalar, G1Projective as BlsG1};
2113
2114    use super::*;
2115    use crate::{
2116        field::{
2117            decomposition::chip::P2RDecompositionChip, foreign::params::MultiEmulationParams,
2118            NativeChip, NativeGadget,
2119        },
2120        instructions::{assertions, control_flow, ecc, equality, public_input, zero},
2121    };
2122
2123    type Native<F> = NativeGadget<F, P2RDecompositionChip<F>, NativeChip<F>>;
2124
2125    type EmulatedField<F, C> = FieldChip<F, <C as Group>::Scalar, MultiEmulationParams, Native<F>>;
2126
2127    macro_rules! test_generic {
2128        ($mod:ident, $op:ident, $native:ty, $curve:ty, $scalar_field:ty, $name:expr) => {
2129            $mod::tests::$op::<
2130                $native,
2131                AssignedForeignPoint<$native, $curve, MultiEmulationParams>,
2132                ForeignWeierstrassEccChip<
2133                    $native,
2134                    $curve,
2135                    MultiEmulationParams,
2136                    $scalar_field,
2137                    Native<$native>,
2138                >,
2139            >($name);
2140        };
2141    }
2142
2143    macro_rules! test {
2144        ($mod:ident, $op:ident) => {
2145            #[test]
2146            fn $op() {
2147                test_generic!($mod, $op, BlsScalar, K256, EmulatedField<BlsScalar, K256>, "foreign_ecc_k256");
2148                test_generic!($mod, $op, BlsScalar, P256, EmulatedField<BlsScalar, P256>, "foreign_ecc_p256");
2149
2150                // a test of BLS over itself, where the scalar field is native
2151                test_generic!($mod, $op, BlsScalar, BlsG1, Native<BlsScalar>, "foreign_ecc_bls_over_bls");
2152            }
2153        };
2154    }
2155
2156    test!(assertions, test_assertions);
2157
2158    test!(public_input, test_public_inputs);
2159
2160    test!(equality, test_is_equal);
2161
2162    test!(zero, test_zero_assertions);
2163    test!(zero, test_is_zero);
2164
2165    test!(control_flow, test_select);
2166    test!(control_flow, test_cond_assert_equal);
2167    test!(control_flow, test_cond_swap);
2168
2169    macro_rules! ecc_test {
2170        ($op:ident, $native:ty, $curve:ty, $scalar_field:ty, $name:expr) => {
2171            ecc::tests::$op::<
2172                $native,
2173                $curve,
2174                ForeignWeierstrassEccChip<
2175                    $native,
2176                    $curve,
2177                    MultiEmulationParams,
2178                    $scalar_field,
2179                    Native<$native>,
2180                >,
2181            >($name);
2182        };
2183    }
2184
2185    macro_rules! ecc_tests {
2186        ($op:ident) => {
2187            #[test]
2188            fn $op() {
2189                ecc_test!($op, BlsScalar, K256, EmulatedField<BlsScalar, K256>, "foreign_ecc_k256");
2190                ecc_test!($op, BlsScalar, P256, EmulatedField<BlsScalar, P256>, "foreign_ecc_p256");
2191
2192                // a test of BLS over itself, where the scalar field is native
2193                ecc_test!($op, BlsScalar, BlsG1, Native<BlsScalar>, "foreign_ecc_bls_over_bls");
2194            }
2195        };
2196    }
2197
2198    ecc_tests!(test_assign);
2199    ecc_tests!(test_assign_without_subgroup_check);
2200    ecc_tests!(test_add);
2201    ecc_tests!(test_double);
2202    ecc_tests!(test_negate);
2203    ecc_tests!(test_msm);
2204    ecc_tests!(test_msm_by_bounded_scalars);
2205    ecc_tests!(test_mul_by_constant);
2206    ecc_tests!(test_coordinates);
2207}