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