Skip to main content

midnight_circuits/ecc/foreign/
ecc_chip.rs

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