Skip to main content

midnight_circuits/ecc/foreign/
edwards_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 (in twisted Edwards form) operations over foreign fields.
15//! This module supports curves of the form `a*x^2 + y^2 = 1 + d*x^2*y^2`, where
16//! `a` is square and `d` is non-square.
17//!
18//! We require that the emulated elliptic curve do not have low-order points.
19//! In particular, the curve (or the relevant subgroup) must have a large prime
20//! order.
21
22use std::{
23    cell::RefCell,
24    collections::HashMap,
25    fmt::Debug,
26    hash::{Hash, Hasher},
27    rc::Rc,
28};
29
30use ff::{Field, PrimeField};
31use group::Group;
32use midnight_curves::{
33    curve25519::{Curve25519, Curve25519Subgroup},
34    ff_ext::Legendre,
35};
36#[cfg(any(test, feature = "testing"))]
37use midnight_proofs::plonk::Instance;
38use midnight_proofs::{
39    circuit::{Chip, Layouter, Value},
40    plonk::{Advice, Column, ConstraintSystem, Error, Fixed, Selector},
41};
42#[cfg(any(test, feature = "testing"))]
43use {
44    crate::testing_utils::{FromScratch, Sampleable},
45    rand::RngCore,
46};
47
48use super::common::{
49    add_1bit_scalar_bases, configure_multi_select_lookup, fill_dynamic_lookup_row, msm_preprocess,
50};
51use crate::{
52    ecc::{
53        curves::{CircuitCurve, EdwardsCurve},
54        foreign::gates::edwards::addition::{self, AdditionConfig},
55    },
56    field::{
57        foreign::{
58            field_chip::{FieldChip, FieldChipConfig},
59            params::FieldEmulationParams,
60        },
61        AssignedNative,
62    },
63    instructions::{
64        ArithInstructions, AssertionInstructions, AssignmentInstructions, ControlFlowInstructions,
65        DecompositionInstructions, EccInstructions, EqualityInstructions, NativeInstructions,
66        PublicInputInstructions, ScalarFieldInstructions, ZeroInstructions,
67    },
68    types::{AssignedBit, AssignedByte, AssignedField, InnerConstants, InnerValue, Instantiable},
69    CircuitField,
70};
71
72/// Number of columns required by the custom gates of this chip.
73pub fn nb_foreign_edwards_chip_columns<F, C, B>() -> usize
74where
75    F: CircuitField,
76    C: EdwardsCurve,
77    B: FieldEmulationParams<F, C::Base>,
78{
79    // Here we only account for the columns that this chip requires for its own
80    // custom gates.
81    // The outer `+ 1` corresponds to the advice column for the index of
82    // `multi_select`.
83    B::NB_LIMBS as usize + std::cmp::max(B::NB_LIMBS as usize, 1 + B::moduli().len()) + 1
84}
85
86/// Foreign Edwards ECC configuration.
87#[derive(Clone, Debug)]
88pub struct ForeignEdwardsEccConfig<C>
89where
90    C: EdwardsCurve,
91{
92    base_field_config: FieldChipConfig,
93    addition_config: AdditionConfig<C>,
94    // Dynamic lookup columns for windowed MSM table selection.
95    q_multi_select: Selector,
96    idx_col_multi_select: Column<Advice>,
97    tag_col_multi_select: Column<Fixed>,
98}
99
100/// Cache of assigned constant points to their known group element values.
101type ConstantPointCache<F, C, B> = Rc<
102    RefCell<HashMap<AssignedForeignEdwardsPoint<F, C, B>, <C as CircuitCurve>::CryptographicGroup>>,
103>;
104
105/// ECC chip to perform foreign Edwards EC operations.
106#[derive(Clone, Debug)]
107pub struct ForeignEdwardsEccChip<F, C, B, S, N>
108where
109    F: CircuitField,
110    C: EdwardsCurve,
111    B: FieldEmulationParams<F, C::Base>,
112    S: ScalarFieldInstructions<F>,
113    S::Scalar: InnerValue<Element = C::ScalarField>,
114    N: NativeInstructions<F>,
115{
116    config: ForeignEdwardsEccConfig<C>,
117    native_gadget: N,
118    base_field_chip: FieldChip<F, C::Base, B, N>,
119    scalar_field_chip: S,
120    /// Per-chip tag counter for dynamic lookup tables (tag 0 is reserved).
121    tag_cnt: Rc<RefCell<u64>>,
122    /// Cache mapping assigned constant points to their known values.
123    constant_cache: ConstantPointCache<F, C, B>,
124}
125
126impl<F, C, B, S, N> ForeignEdwardsEccChip<F, C, B, S, N>
127where
128    F: CircuitField,
129    C: EdwardsCurve,
130    C::Base: Legendre,
131    B: FieldEmulationParams<F, C::Base>,
132    S: ScalarFieldInstructions<F>,
133    S::Scalar: InnerValue<Element = C::ScalarField>,
134    N: NativeInstructions<F>,
135{
136    /// Configures the foreign Edwards ECC chip.
137    pub fn configure(
138        meta: &mut ConstraintSystem<F>,
139        base_field_config: &FieldChipConfig,
140        advice_columns: &[Column<Advice>],
141        fixed_columns: &[Column<Fixed>],
142        nb_parallel_range_checks: usize,
143        max_bit_len: u32,
144    ) -> ForeignEdwardsEccConfig<C> {
145        assert!(C::A.legendre() == 1);
146        assert!(C::D.legendre() == -1);
147
148        let addition_config = AdditionConfig::<C>::configure::<F, B>(
149            meta,
150            base_field_config,
151            fixed_columns[0],
152            nb_parallel_range_checks,
153            max_bit_len,
154        );
155
156        let (q_multi_select, idx_col_multi_select, tag_col_multi_select) =
157            configure_multi_select_lookup(meta, advice_columns, base_field_config);
158
159        ForeignEdwardsEccConfig {
160            base_field_config: base_field_config.clone(),
161            addition_config,
162            q_multi_select,
163            idx_col_multi_select,
164            tag_col_multi_select,
165        }
166    }
167}
168
169impl<F, C, B, S, N> ForeignEdwardsEccChip<F, C, B, S, N>
170where
171    F: CircuitField,
172    C: EdwardsCurve,
173    B: FieldEmulationParams<F, C::Base>,
174    S: ScalarFieldInstructions<F>,
175    S::Scalar: InnerValue<Element = C::ScalarField>,
176    N: NativeInstructions<F>,
177{
178    /// Creates new foreign Edwards ECC chip from its building blocks.
179    pub fn new(
180        config: &ForeignEdwardsEccConfig<C>,
181        native_gadget: &N,
182        scalar_field_chip: &S,
183    ) -> Self {
184        let base_field_chip = FieldChip::new(&config.base_field_config, native_gadget);
185        Self {
186            config: config.clone(),
187            native_gadget: native_gadget.clone(),
188            base_field_chip,
189            scalar_field_chip: scalar_field_chip.clone(),
190            tag_cnt: Rc::new(RefCell::new(1)),
191            constant_cache: Rc::new(RefCell::new(HashMap::new())),
192        }
193    }
194
195    /// The emulated base field chip of this foreign Edwards ECC chip.
196    pub fn base_field_chip(&self) -> &FieldChip<F, C::Base, B, N> {
197        &self.base_field_chip
198    }
199
200    /// Returns the constant value of `point` if it was created via
201    /// `assign_fixed`, or `None` otherwise.
202    pub fn as_known_constant(
203        &self,
204        point: &AssignedForeignEdwardsPoint<F, C, B>,
205    ) -> Option<C::CryptographicGroup> {
206        self.constant_cache.borrow().get(point).copied()
207    }
208
209    /// A chip with instructions for the scalar field of this ECC chip.
210    pub fn scalar_field_chip(&self) -> &S {
211        &self.scalar_field_chip
212    }
213}
214
215impl<F, B, S, N> ForeignEdwardsEccChip<F, Curve25519, B, S, N>
216where
217    F: CircuitField,
218    B: FieldEmulationParams<F, <Curve25519 as CircuitCurve>::Base>,
219    S: ScalarFieldInstructions<F>,
220    S::Scalar: InnerValue<Element = <Curve25519 as CircuitCurve>::ScalarField>,
221    N: NativeInstructions<F>,
222{
223    /// In-circuit compression of a given subgroup point into canonical
224    /// little-endian bytes.
225    ///
226    /// Let p = 2^255 -19 be the base field modulus.
227    ///
228    /// A curve point (x,y), with coordinates in the range 0 <= x,y < p, is
229    /// encoded as follows. First, encode the y-coordinate as a little-endian
230    /// array of 32 bytes. The most significant bit of the final byte (i.e., the
231    /// most significant byte) is always zero. To form the encoding of the
232    /// point, copy the least significant bit of the x-coordinate to the
233    /// most significant bit of the final byte of the y-coordinate.
234    ///
235    /// # Returns
236    /// An array [`AssignedByte<F>`; 32] constrained to represent a canonical
237    /// encoding.
238    pub fn to_canonical_compressed_bytes(
239        &self,
240        layouter: &mut impl Layouter<F>,
241        point: &AssignedForeignEdwardsPoint<F, Curve25519, B>,
242    ) -> Result<[AssignedByte<F>; 32], Error> {
243        // Decomposition into (LE) bytes enforces canonicity.
244        let mut y_bytes = self.base_field_chip().assigned_to_le_bytes(
245            layouter,
246            &self.y_coordinate(point),
247            None,
248        )?;
249
250        let x_bits = self.base_field_chip().assigned_to_le_bits(
251            layouter,
252            &self.x_coordinate(point),
253            Some(255),
254            true,
255        )?;
256
257        // Encode the sign bit of x (= x mod 2, i.e., the least significant bit of x)
258        // into the most significant byte of y: MSB = MSB of y + LSBit of x * 128.
259        //
260        // (This is safe: y <= p - 1 = 2^255 - 19 - 1, which means MSB of y <= 127;
261        // hence, adding 128 causes _no_ overflow.)
262        let last_byte: AssignedNative<F> = self.native_gadget.linear_combination(
263            layouter,
264            &[
265                (F::ONE, y_bytes[y_bytes.len() - 1].clone().into()),
266                (F::from(128), x_bits[0].clone().into()),
267            ],
268            F::ZERO,
269        )?;
270
271        let last = y_bytes.len() - 1;
272        y_bytes[last] = self.native_gadget.convert_unsafe(layouter, &last_byte)?;
273
274        Ok(y_bytes.try_into().expect("exactly 32 bytes"))
275    }
276
277    /// In-circuit decompression of little-endian canonical compressed bytes.
278    ///
279    /// Decoding a point, given as an array of 32 bytes, works as follows: The
280    /// caller of this function provides the claimed decoded point as a
281    /// witness. The function loads this point into the circuit,
282    /// calls [Self::to_canonical_compressed_bytes] and checks if the
283    /// resulting byte encoding matches the provided byte encoding.
284    ///
285    /// # Returns
286    /// An [AssignedForeignEdwardsPoint] constrained to lie in the subgroup.
287    ///
288    /// # Unsatisfiable Circuit
289    /// If the given array of [AssignedByte] is a non-canonical encoding of the
290    /// point provided by [`Value<Curve25519Subgroup>`].
291    pub fn from_canonical_compressed_bytes(
292        &self,
293        layouter: &mut impl Layouter<F>,
294        compressed_bytes: &[AssignedByte<F>; 32],
295        value: Value<Curve25519Subgroup>,
296    ) -> Result<AssignedForeignEdwardsPoint<F, Curve25519, B>, Error> {
297        let point = self.assign(layouter, value)?;
298        let canonical_bytes = self.to_canonical_compressed_bytes(layouter, &point)?;
299        compressed_bytes.iter().zip(canonical_bytes.iter()).try_for_each(
300            |(com_byte, can_byte)| self.native_gadget.assert_equal(layouter, com_byte, can_byte),
301        )?;
302
303        Ok(point)
304    }
305}
306
307/// Type for foreign Edwards EC points.
308#[derive(Clone, Debug)]
309#[must_use]
310pub struct AssignedForeignEdwardsPoint<F, C, B>
311where
312    F: CircuitField,
313    C: EdwardsCurve,
314    B: FieldEmulationParams<F, C::Base>,
315{
316    point: Value<C::CryptographicGroup>,
317    x: AssignedField<F, C::Base, B>,
318    y: AssignedField<F, C::Base, B>,
319}
320
321impl<F, C, B> PartialEq for AssignedForeignEdwardsPoint<F, C, B>
322where
323    F: CircuitField,
324    C: EdwardsCurve,
325    B: FieldEmulationParams<F, C::Base>,
326{
327    fn eq(&self, other: &Self) -> bool {
328        self.x == other.x && self.y == other.y
329    }
330}
331
332impl<F, C, B> Eq for AssignedForeignEdwardsPoint<F, C, B>
333where
334    F: CircuitField,
335    C: EdwardsCurve,
336    B: FieldEmulationParams<F, C::Base>,
337{
338}
339
340impl<F, C, B> Hash for AssignedForeignEdwardsPoint<F, C, B>
341where
342    F: CircuitField,
343    C: EdwardsCurve,
344    B: FieldEmulationParams<F, C::Base>,
345{
346    fn hash<H: Hasher>(&self, state: &mut H) {
347        self.x.hash(state);
348        self.y.hash(state);
349    }
350}
351
352impl<F, C, B> Instantiable<F> for AssignedForeignEdwardsPoint<F, C, B>
353where
354    F: CircuitField,
355    C: EdwardsCurve,
356    B: FieldEmulationParams<F, C::Base>,
357{
358    fn as_public_input(p: &C::CryptographicGroup) -> Vec<F> {
359        let (x, y) = (*p).into().coordinates().expect("Edwards coordinates cannot fail");
360        [
361            AssignedField::<F, C::Base, B>::as_public_input(&x).as_slice(),
362            AssignedField::<F, C::Base, B>::as_public_input(&y).as_slice(),
363        ]
364        .concat()
365    }
366
367    fn from_public_input(fields: &[F]) -> Option<C::CryptographicGroup> {
368        let nb_limbs_per_batch = (F::CAPACITY / B::LOG2_BASE) as usize;
369        let nb_pi_per_coord = (B::NB_LIMBS as usize).div_ceil(nb_limbs_per_batch);
370        if fields.len() != 2 * nb_pi_per_coord {
371            return None;
372        }
373        let x = AssignedField::<F, C::Base, B>::from_public_input(&fields[..nb_pi_per_coord])?;
374        let y = AssignedField::<F, C::Base, B>::from_public_input(&fields[nb_pi_per_coord..])?;
375        C::from_xy(x, y).map(|p| p.into_subgroup())
376    }
377}
378
379impl<F, C, B> InnerValue for AssignedForeignEdwardsPoint<F, C, B>
380where
381    F: CircuitField,
382    C: EdwardsCurve,
383    B: FieldEmulationParams<F, C::Base>,
384{
385    type Element = C::CryptographicGroup;
386
387    fn value(&self) -> Value<Self::Element> {
388        self.point
389    }
390}
391
392impl<F, C, B> InnerConstants for AssignedForeignEdwardsPoint<F, C, B>
393where
394    F: CircuitField,
395    C: EdwardsCurve,
396    B: FieldEmulationParams<F, C::Base>,
397{
398    fn inner_zero() -> C::CryptographicGroup {
399        C::CryptographicGroup::identity()
400    }
401
402    fn inner_one() -> Self::Element {
403        C::CryptographicGroup::generator()
404    }
405}
406
407#[cfg(any(test, feature = "testing"))]
408impl<F, C, B> Sampleable for AssignedForeignEdwardsPoint<F, C, B>
409where
410    F: CircuitField,
411    C: EdwardsCurve,
412    B: FieldEmulationParams<F, C::Base>,
413{
414    fn sample_inner(rng: impl RngCore) -> C::CryptographicGroup {
415        C::CryptographicGroup::random(rng)
416    }
417}
418
419impl<F, C, B, S, N> Chip<F> for ForeignEdwardsEccChip<F, C, B, S, N>
420where
421    F: CircuitField,
422    C: EdwardsCurve,
423    B: FieldEmulationParams<F, C::Base>,
424    S: ScalarFieldInstructions<F>,
425    S::Scalar: InnerValue<Element = C::ScalarField>,
426    N: NativeInstructions<F>,
427{
428    type Config = ForeignEdwardsEccConfig<C>;
429    type Loaded = ();
430    fn config(&self) -> &Self::Config {
431        &self.config
432    }
433    fn loaded(&self) -> &Self::Loaded {
434        &()
435    }
436}
437
438impl<F, C, B, S, N> ForeignEdwardsEccChip<F, C, B, S, N>
439where
440    F: CircuitField,
441    C: EdwardsCurve,
442    B: FieldEmulationParams<F, C::Base>,
443    S: ScalarFieldInstructions<F>,
444    S::Scalar: InnerValue<Element = C::ScalarField>,
445    N: NativeInstructions<F>,
446{
447    /// Converts a subgroup point to [AssignedForeignEdwardsPoint].
448    /// The point is _not_ asserted (with constraints) to be on the curve.
449    fn assign_point_unchecked(
450        &self,
451        layouter: &mut impl Layouter<F>,
452        value: Value<C::CryptographicGroup>,
453    ) -> Result<AssignedForeignEdwardsPoint<F, C, B>, Error> {
454        let (val_x, val_y) = value
455            .map(|v| v.into().coordinates().expect("Edwards coordinates cannot fail"))
456            .unzip();
457        let x = self.base_field_chip().assign(layouter, val_x)?;
458        let y = self.base_field_chip().assign(layouter, val_y)?;
459        let p = AssignedForeignEdwardsPoint::<F, C, B> { point: value, x, y };
460
461        Ok(p)
462    }
463
464    /// Asserts the curve equation `a*x^2 + y^2 = 1 + d*x^2*y^2` of an emulated
465    /// twisted Edwards curve, given the x and y coordinates in form of
466    /// [AssignedField].
467    fn assert_on_curve(
468        &self,
469        layouter: &mut impl Layouter<F>,
470        x: &AssignedField<F, C::Base, B>,
471        y: &AssignedField<F, C::Base, B>,
472    ) -> Result<(), Error> {
473        let base_chip = self.base_field_chip();
474
475        // Compute x^2, y^2 and a*x^2 + y^2 - 1 - d*x^2*y^2 in-circuit
476        let x_sq = base_chip.mul(layouter, x, x, None)?;
477        let y_sq = base_chip.mul(layouter, y, y, None)?;
478        let d_xy_sq = base_chip.mul(layouter, &x_sq, &y_sq, Some(C::D))?;
479        let lhs = base_chip.linear_combination(
480            layouter,
481            &[(C::A, x_sq), (C::Base::ONE, y_sq)],
482            -C::Base::ONE,
483        )?;
484
485        // Assert a*x^2 + y^2 - 1 = d*x^2*y^2
486        base_chip.assert_equal(layouter, &lhs, &d_xy_sq)
487    }
488
489    /// Adds an assigned point `p` to a constant point `q_val`. Cheaper than
490    /// general `add` because the constant coordinates turn emulated `mul`
491    /// calls into `mul_by_constant` calls.
492    fn add_constant(
493        &self,
494        layouter: &mut impl Layouter<F>,
495        p: &AssignedForeignEdwardsPoint<F, C, B>,
496        q_val: C::CryptographicGroup,
497    ) -> Result<AssignedForeignEdwardsPoint<F, C, B>, Error> {
498        // If both operands are known constants, compute off-circuit.
499        if let Some(pv) = self.as_known_constant(p) {
500            return self.assign_fixed(layouter, pv + q_val);
501        }
502
503        let (qx, qy) = q_val.into().coordinates().expect("Edwards coordinates cannot fail");
504
505        let base_chip = self.base_field_chip();
506
507        let r_value = p.value().map(|pv| pv + q_val);
508        let r = self.assign_point_unchecked(layouter, r_value)?;
509
510        let px_qx = base_chip.mul_by_constant(layouter, &p.x, qx)?;
511        let py_qy = base_chip.mul_by_constant(layouter, &p.y, qy)?;
512        let px_qy = base_chip.mul_by_constant(layouter, &p.x, qy)?;
513        let py_qx = base_chip.mul_by_constant(layouter, &p.y, qx)?;
514        let neg_a_px_qx = base_chip.mul_by_constant(layouter, &px_qx, -C::A)?;
515        let d_px_py_qx_qy = base_chip.mul(layouter, &px_qx, &py_qy, Some(C::D))?;
516
517        // Rx * (1 + d * Px * Py * Qx * Qy) = (Px * Qy + Py * Qx)
518        addition::assert_addition_coordinate(
519            layouter,
520            &r.x,
521            &px_qy,
522            &py_qx,
523            &d_px_py_qx_qy,
524            false,
525            base_chip,
526            &self.config.addition_config,
527        )?;
528
529        // Ry * (1 - d * Px * Py * Qx * Qy) = (Py * Qy - a * Px * Qx)
530        addition::assert_addition_coordinate(
531            layouter,
532            &r.y,
533            &py_qy,
534            &neg_a_px_qx,
535            &d_px_py_qx_qy,
536            true,
537            base_chip,
538            &self.config.addition_config,
539        )?;
540
541        Ok(AssignedForeignEdwardsPoint {
542            point: r_value,
543            x: r.x,
544            y: r.y,
545        })
546    }
547}
548
549impl<F, C, B, S, N> AssignmentInstructions<F, AssignedForeignEdwardsPoint<F, C, B>>
550    for ForeignEdwardsEccChip<F, C, B, S, N>
551where
552    F: CircuitField,
553    C: EdwardsCurve,
554    B: FieldEmulationParams<F, C::Base>,
555    S: ScalarFieldInstructions<F>,
556    S::Scalar: InnerValue<Element = C::ScalarField>,
557    N: NativeInstructions<F>,
558{
559    fn assign(
560        &self,
561        layouter: &mut impl Layouter<F>,
562        p_value: Value<C::CryptographicGroup>,
563    ) -> Result<AssignedForeignEdwardsPoint<F, C, B>, Error> {
564        // Let h be the cofactor of the subgroup.
565        //
566        // Instead of witnessing P, we witness an h-root Q, and return h * Q.
567        // This guarantess that the returned point is in the desired subgroup.
568        let cofactor = C::ScalarField::from_u128(C::COFACTOR);
569        let q =
570            self.assign_point_unchecked(layouter, p_value.map(|p| p * cofactor.invert().unwrap()))?;
571
572        self.assert_on_curve(layouter, &q.x, &q.y)?;
573        self.mul_by_constant(layouter, cofactor, &q)
574    }
575
576    fn assign_fixed(
577        &self,
578        layouter: &mut impl Layouter<F>,
579        constant: C::CryptographicGroup,
580    ) -> Result<AssignedForeignEdwardsPoint<F, C, B>, Error> {
581        let (x, y) = constant.into().coordinates().expect("Edwards coordinates cannot fail");
582        let x = self.base_field_chip().assign_fixed(layouter, x)?;
583        let y = self.base_field_chip().assign_fixed(layouter, y)?;
584
585        let p = AssignedForeignEdwardsPoint::<F, C, B> {
586            point: Value::known(constant),
587            x,
588            y,
589        };
590        self.constant_cache.borrow_mut().insert(p.clone(), constant);
591        Ok(p)
592    }
593}
594
595impl<F, C, B, S, N> PublicInputInstructions<F, AssignedForeignEdwardsPoint<F, C, B>>
596    for ForeignEdwardsEccChip<F, C, B, S, N>
597where
598    F: CircuitField,
599    C: EdwardsCurve,
600    B: FieldEmulationParams<F, C::Base>,
601    S: ScalarFieldInstructions<F>,
602    S::Scalar: InnerValue<Element = C::ScalarField>,
603    N: NativeInstructions<F> + PublicInputInstructions<F, AssignedBit<F>>,
604{
605    fn as_public_input(
606        &self,
607        layouter: &mut impl Layouter<F>,
608        p: &AssignedForeignEdwardsPoint<F, C, B>,
609    ) -> Result<Vec<AssignedNative<F>>, Error> {
610        Ok([
611            self.base_field_chip.as_public_input(layouter, &p.x)?.as_slice(),
612            self.base_field_chip.as_public_input(layouter, &p.y)?.as_slice(),
613        ]
614        .concat())
615    }
616
617    fn constrain_as_public_input(
618        &self,
619        layouter: &mut impl Layouter<F>,
620        assigned: &AssignedForeignEdwardsPoint<F, C, B>,
621    ) -> Result<(), Error> {
622        self.as_public_input(layouter, assigned)?
623            .iter()
624            .try_for_each(|c| self.native_gadget.constrain_as_public_input(layouter, c))
625    }
626
627    fn assign_as_public_input(
628        &self,
629        layouter: &mut impl Layouter<F>,
630        value: Value<C::CryptographicGroup>,
631    ) -> Result<AssignedForeignEdwardsPoint<F, C, B>, Error> {
632        let point = self.assign(layouter, value)?;
633        self.constrain_as_public_input(layouter, &point)?;
634        Ok(point)
635    }
636}
637
638/// Inherit assignment instructions for [AssignedField], from the
639/// `scalar_field_chip`.
640impl<F, C, B, S, SP, N> AssignmentInstructions<F, AssignedField<F, C::ScalarField, SP>>
641    for ForeignEdwardsEccChip<F, C, B, S, N>
642where
643    F: CircuitField,
644    C: EdwardsCurve,
645    B: FieldEmulationParams<F, C::Base>,
646    S: ScalarFieldInstructions<F, Scalar = AssignedField<F, C::ScalarField, SP>>,
647    SP: FieldEmulationParams<F, C::ScalarField>,
648    N: NativeInstructions<F>,
649{
650    fn assign(
651        &self,
652        layouter: &mut impl Layouter<F>,
653        value: Value<<S::Scalar as InnerValue>::Element>,
654    ) -> Result<S::Scalar, Error> {
655        self.scalar_field_chip().assign(layouter, value)
656    }
657
658    fn assign_fixed(
659        &self,
660        layouter: &mut impl Layouter<F>,
661        constant: <S::Scalar as InnerValue>::Element,
662    ) -> Result<S::Scalar, Error> {
663        self.scalar_field_chip().assign_fixed(layouter, constant)
664    }
665}
666
667impl<F, C, B, S, N> AssertionInstructions<F, AssignedForeignEdwardsPoint<F, C, B>>
668    for ForeignEdwardsEccChip<F, C, B, S, N>
669where
670    F: CircuitField,
671    C: EdwardsCurve,
672    B: FieldEmulationParams<F, C::Base>,
673    S: ScalarFieldInstructions<F>,
674    S::Scalar: InnerValue<Element = C::ScalarField>,
675    N: NativeInstructions<F>,
676{
677    fn assert_equal(
678        &self,
679        layouter: &mut impl Layouter<F>,
680        p: &AssignedForeignEdwardsPoint<F, C, B>,
681        q: &AssignedForeignEdwardsPoint<F, C, B>,
682    ) -> Result<(), Error> {
683        self.base_field_chip().assert_equal(layouter, &p.x, &q.x)?;
684        self.base_field_chip().assert_equal(layouter, &p.y, &q.y)
685    }
686
687    fn assert_not_equal(
688        &self,
689        layouter: &mut impl Layouter<F>,
690        p: &AssignedForeignEdwardsPoint<F, C, B>,
691        q: &AssignedForeignEdwardsPoint<F, C, B>,
692    ) -> Result<(), Error> {
693        let p_eq_q = self.is_equal(layouter, p, q)?;
694        self.native_gadget.assert_equal_to_fixed(layouter, &p_eq_q, false)
695    }
696
697    fn assert_equal_to_fixed(
698        &self,
699        layouter: &mut impl Layouter<F>,
700        p: &AssignedForeignEdwardsPoint<F, C, B>,
701        constant: C::CryptographicGroup,
702    ) -> Result<(), Error> {
703        let coordinates = constant.into().coordinates().expect("Edwards coordinates cannot fail");
704        self.base_field_chip().assert_equal_to_fixed(layouter, &p.x, coordinates.0)?;
705        self.base_field_chip().assert_equal_to_fixed(layouter, &p.y, coordinates.1)
706    }
707
708    fn assert_not_equal_to_fixed(
709        &self,
710        layouter: &mut impl Layouter<F>,
711        p: &AssignedForeignEdwardsPoint<F, C, B>,
712        constant: C::CryptographicGroup,
713    ) -> Result<(), Error> {
714        let p_eq_constant = self.is_equal_to_fixed(layouter, p, constant)?;
715        self.native_gadget.assert_equal_to_fixed(layouter, &p_eq_constant, false)
716    }
717}
718
719impl<F, C, B, S, N> EqualityInstructions<F, AssignedForeignEdwardsPoint<F, C, B>>
720    for ForeignEdwardsEccChip<F, C, B, S, N>
721where
722    F: CircuitField,
723    C: EdwardsCurve,
724    B: FieldEmulationParams<F, C::Base>,
725    S: ScalarFieldInstructions<F>,
726    S::Scalar: InnerValue<Element = C::ScalarField>,
727    N: NativeInstructions<F>,
728{
729    fn is_equal(
730        &self,
731        layouter: &mut impl Layouter<F>,
732        p: &AssignedForeignEdwardsPoint<F, C, B>,
733        q: &AssignedForeignEdwardsPoint<F, C, B>,
734    ) -> Result<AssignedBit<F>, Error> {
735        let eq_x = self.base_field_chip().is_equal(layouter, &p.x, &q.x)?;
736        let eq_y = self.base_field_chip().is_equal(layouter, &p.y, &q.y)?;
737        self.native_gadget.and(layouter, &[eq_x, eq_y])
738    }
739
740    fn is_equal_to_fixed(
741        &self,
742        layouter: &mut impl Layouter<F>,
743        p: &AssignedForeignEdwardsPoint<F, C, B>,
744        constant: <AssignedForeignEdwardsPoint<F, C, B> as InnerValue>::Element,
745    ) -> Result<AssignedBit<F>, Error> {
746        let coordinates = constant.into().coordinates().expect("Edwards coordinates cannot fail");
747        let eq_x = self.base_field_chip().is_equal_to_fixed(layouter, &p.x, coordinates.0)?;
748        let eq_y = self.base_field_chip().is_equal_to_fixed(layouter, &p.y, coordinates.1)?;
749        self.native_gadget.and(layouter, &[eq_x, eq_y])
750    }
751
752    fn is_not_equal(
753        &self,
754        layouter: &mut impl Layouter<F>,
755        x: &AssignedForeignEdwardsPoint<F, C, B>,
756        y: &AssignedForeignEdwardsPoint<F, C, B>,
757    ) -> Result<AssignedBit<F>, Error> {
758        let b = self.is_equal(layouter, x, y)?;
759        self.native_gadget.not(layouter, &b)
760    }
761
762    fn is_not_equal_to_fixed(
763        &self,
764        layouter: &mut impl Layouter<F>,
765        x: &AssignedForeignEdwardsPoint<F, C, B>,
766        constant: <AssignedForeignEdwardsPoint<F, C, B> as InnerValue>::Element,
767    ) -> Result<AssignedBit<F>, Error> {
768        let b = self.is_equal_to_fixed(layouter, x, constant)?;
769        self.native_gadget.not(layouter, &b)
770    }
771}
772
773impl<F, C, B, S, N> ZeroInstructions<F, AssignedForeignEdwardsPoint<F, C, B>>
774    for ForeignEdwardsEccChip<F, C, B, S, N>
775where
776    F: CircuitField,
777    C: EdwardsCurve,
778    B: FieldEmulationParams<F, C::Base>,
779    S: ScalarFieldInstructions<F>,
780    S::Scalar: InnerValue<Element = C::ScalarField>,
781    N: NativeInstructions<F>,
782{
783    fn is_zero(
784        &self,
785        layouter: &mut impl Layouter<F>,
786        p: &AssignedForeignEdwardsPoint<F, C, B>,
787    ) -> Result<AssignedBit<F>, Error> {
788        self.is_equal_to_fixed(layouter, p, C::CryptographicGroup::identity())
789    }
790}
791
792impl<F, C, B, S, N> ControlFlowInstructions<F, AssignedForeignEdwardsPoint<F, C, B>>
793    for ForeignEdwardsEccChip<F, C, B, S, N>
794where
795    F: CircuitField,
796    C: EdwardsCurve,
797    B: FieldEmulationParams<F, C::Base>,
798    S: ScalarFieldInstructions<F>,
799    S::Scalar: InnerValue<Element = C::ScalarField>,
800    N: NativeInstructions<F>,
801{
802    /// Returns `p` if `cond = 1` and `q` otherwise. In essence, this enforces
803    /// `cond * p + (1 - cond) * q = 0` over the emulated twisted Edwards curve.
804    fn select(
805        &self,
806        layouter: &mut impl Layouter<F>,
807        cond: &AssignedBit<F>,
808        p: &AssignedForeignEdwardsPoint<F, C, B>,
809        q: &AssignedForeignEdwardsPoint<F, C, B>,
810    ) -> Result<AssignedForeignEdwardsPoint<F, C, B>, Error> {
811        let point = p.point.zip(q.point).zip(cond.value()).map(|((p, q), b)| if b { p } else { q });
812        let x = self.base_field_chip().select(layouter, cond, &p.x, &q.x)?;
813        let y = self.base_field_chip().select(layouter, cond, &p.y, &q.y)?;
814        Ok(AssignedForeignEdwardsPoint::<F, C, B> { point, x, y })
815    }
816}
817
818impl<F, C, B, S, N> EccInstructions<F, C> for ForeignEdwardsEccChip<F, C, B, S, N>
819where
820    F: CircuitField,
821    C: EdwardsCurve,
822    B: FieldEmulationParams<F, C::Base>,
823    S: ScalarFieldInstructions<F>,
824    S::Scalar: InnerValue<Element = C::ScalarField>,
825    N: NativeInstructions<F>,
826{
827    type Point = AssignedForeignEdwardsPoint<F, C, B>;
828    type Coordinate = AssignedField<F, C::Base, B>;
829    type Scalar = S::Scalar;
830
831    fn add(
832        &self,
833        layouter: &mut impl Layouter<F>,
834        p: &Self::Point,
835        q: &Self::Point,
836    ) -> Result<Self::Point, Error> {
837        if p == q {
838            return self.double(layouter, p);
839        }
840
841        // If one operand is constant, use the cheaper `add_constant` path.
842        if let Some(qv) = self.as_known_constant(q) {
843            return self.add_constant(layouter, p, qv);
844        }
845        if let Some(pv) = self.as_known_constant(p) {
846            return self.add_constant(layouter, q, pv);
847        }
848
849        // Complete addition law on twisted Edwards curve:
850        // (see https://eprint.iacr.org/2008/013.pdf)
851        //
852        // P + Q = R
853        // <=>
854        // (Px, Py) + (Qx, Qy) = (Rx, Ry)
855        // <=>
856        // Rx = (Px * Qy +     Py * Qx) / (1 + d * Px * Py * Qx * Qy)
857        // Ry = (Py * Qy - a * Px * Qx) / (1 - d * Px * Py * Qx * Qy)
858        // <=> (denominators are non-zero)
859        // Rx * (1 + d * Px * Py * Qx * Qy) = (Px * Qy +     Py * Qx)
860        // Ry * (1 - d * Px * Py * Qx * Qy) = (Py * Qy - a * Px * Qx)
861
862        let base_chip = self.base_field_chip();
863
864        let r_value = p.value().zip(q.value()).map(|(p, q)| p + q);
865        let r = self.assign_point_unchecked(layouter, r_value)?;
866
867        let px_qx = base_chip.mul(layouter, &p.x, &q.x, None)?;
868        let py_qy = base_chip.mul(layouter, &p.y, &q.y, None)?;
869        let px_qy = base_chip.mul(layouter, &p.x, &q.y, None)?;
870        let py_qx = base_chip.mul(layouter, &p.y, &q.x, None)?;
871        let neg_a_px_qx = base_chip.mul_by_constant(layouter, &px_qx, -C::A)?;
872        let d_px_py_qx_qy = base_chip.mul(layouter, &px_qx, &py_qy, Some(C::D))?;
873
874        // Constraint for Rx coordinate
875        // Rx * (1 + d * Px * Py * Qx * Qy) = (Px * Qy + Py * Qx)
876        addition::assert_addition_coordinate(
877            layouter,
878            &r.x,
879            &px_qy,
880            &py_qx,
881            &d_px_py_qx_qy,
882            false,
883            base_chip,
884            &self.config.addition_config,
885        )?;
886
887        // Constraint for Ry coordinate
888        // Ry * (1 - d * Px * Py * Qx * Qy) = (Py * Qy - a * Px * Qx)
889        addition::assert_addition_coordinate(
890            layouter,
891            &r.y,
892            &py_qy,
893            &neg_a_px_qx,
894            &d_px_py_qx_qy,
895            true,
896            base_chip,
897            &self.config.addition_config,
898        )?;
899
900        Ok(AssignedForeignEdwardsPoint {
901            point: r_value,
902            x: r.x,
903            y: r.y,
904        })
905    }
906
907    fn double(
908        &self,
909        layouter: &mut impl Layouter<F>,
910        p: &AssignedForeignEdwardsPoint<F, C, B>,
911    ) -> Result<AssignedForeignEdwardsPoint<F, C, B>, Error> {
912        if let Some(pv) = self.as_known_constant(p) {
913            return self.assign_fixed(layouter, pv + pv);
914        }
915
916        // Complete doubling on twisted Edwards curve.
917        // (see https://eprint.iacr.org/2008/013.pdf)
918        //
919        // P + P = R
920        // <=>
921        // (Px, Py) + (Px, Py) = (Rx, Ry)
922        // <=>
923        // Rx = (Px * Py +     Py * Px) / (1 + d * Px * Py * Px * Py)
924        // Ry = (Py * Py - a * Px * Px) / (1 - d * Px * Py * Px * Py)
925        // <=> (denominators are non-zero)
926        // Rx * (1 + d * Px^2 * Py^2) = 2 * Px * Py
927        // Ry * (1 - d * Px^2 * Py^2) = Py^2 - a * Px^2
928        //
929        // Since P is on the curve: a * Px^2 + Py^2 = 1 + d * Px^2 * Py^2,
930        // we substitute w = a * Px^2 + Py^2 - 1 for d * Px^2 * Py^2,
931        // saving one emulated multiplication.
932
933        let base_chip = self.base_field_chip();
934
935        let r_value = p.value().map(|p| p + p);
936        let r = self.assign_point_unchecked(layouter, r_value)?;
937
938        let px_sq = base_chip.mul(layouter, &p.x, &p.x, None)?;
939        let py_sq = base_chip.mul(layouter, &p.y, &p.y, None)?;
940        let px_py = base_chip.mul(layouter, &p.x, &p.y, None)?;
941
942        let neg_a_px_sq = base_chip.mul_by_constant(layouter, &px_sq, -C::A)?;
943
944        // w = d * Px^2 * Py^2 = a * Px^2 + Py^2 - 1  (on-curve relation)
945        let w = base_chip.linear_combination(
946            layouter,
947            &[(C::A, px_sq), (C::Base::ONE, py_sq.clone())],
948            -C::Base::ONE,
949        )?;
950
951        // Rx * (1 + w) = 2 * Px * Py
952        addition::assert_addition_coordinate(
953            layouter,
954            &r.x,
955            &px_py,
956            &px_py,
957            &w,
958            false,
959            base_chip,
960            &self.config.addition_config,
961        )?;
962
963        // Ry * (1 - w) = Py^2 - a * Px^2
964        addition::assert_addition_coordinate(
965            layouter,
966            &r.y,
967            &py_sq,
968            &neg_a_px_sq,
969            &w,
970            true,
971            base_chip,
972            &self.config.addition_config,
973        )?;
974
975        Ok(AssignedForeignEdwardsPoint {
976            point: r_value,
977            x: r.x,
978            y: r.y,
979        })
980    }
981
982    fn negate(
983        &self,
984        layouter: &mut impl Layouter<F>,
985        p: &Self::Point,
986    ) -> Result<Self::Point, Error> {
987        if let Some(pv) = self.as_known_constant(p) {
988            return self.assign_fixed(layouter, -pv);
989        }
990
991        // The negation of `P = (x, y)` on a twisted Edwards curve is `-P = (-x, y)`
992        let neg_x = self.base_field_chip().neg(layouter, &p.x)?;
993        let neg_x = self.base_field_chip().normalize(layouter, &neg_x)?;
994        Ok(AssignedForeignEdwardsPoint::<F, C, B> {
995            point: -p.point,
996            x: neg_x,
997            y: p.y.clone(),
998        })
999    }
1000
1001    fn msm(
1002        &self,
1003        layouter: &mut impl Layouter<F>,
1004        scalars: &[Self::Scalar],
1005        bases: &[Self::Point],
1006    ) -> Result<Self::Point, Error> {
1007        let scalars = scalars
1008            .iter()
1009            .map(|s| (s.clone(), C::ScalarField::NUM_BITS as usize))
1010            .collect::<Vec<_>>();
1011        self.msm_by_bounded_scalars(layouter, &scalars, bases)
1012    }
1013
1014    fn msm_by_bounded_scalars(
1015        &self,
1016        layouter: &mut impl Layouter<F>,
1017        scalars: &[(S::Scalar, usize)],
1018        bases: &[AssignedForeignEdwardsPoint<F, C, B>],
1019    ) -> Result<AssignedForeignEdwardsPoint<F, C, B>, Error> {
1020        if scalars.len() != bases.len() {
1021            panic!("Number of scalars and points should be the same.")
1022        }
1023        let scalar_chip = self.scalar_field_chip();
1024
1025        let (scalars, bases, bases_with_1bit_scalar) =
1026            msm_preprocess(self, scalar_chip, layouter, scalars, bases)?;
1027
1028        // Decompose scalars to bits with tight bound, then chunk into windows.
1029        const WS: usize = 4;
1030        let scalar_windows: Vec<Vec<AssignedNative<F>>> = scalars
1031            .iter()
1032            .map(|(s, num_bits)| {
1033                let bits = scalar_chip.assigned_to_le_bits(layouter, s, Some(*num_bits), true)?;
1034                bits.chunks(WS)
1035                    .map(|chunk| self.native_gadget.assigned_from_le_bits(layouter, chunk))
1036                    .collect::<Result<Vec<_>, _>>()
1037            })
1038            .collect::<Result<Vec<_>, _>>()?;
1039
1040        let res = self.windowed_msm::<WS>(layouter, &scalar_windows, &bases)?;
1041
1042        // Add 1-bit scalar bases.
1043        add_1bit_scalar_bases(layouter, self, scalar_chip, &bases_with_1bit_scalar, res)
1044    }
1045
1046    fn mul_by_constant(
1047        &self,
1048        layouter: &mut impl Layouter<F>,
1049        scalar: C::ScalarField,
1050        base: &AssignedForeignEdwardsPoint<F, C, B>,
1051    ) -> Result<AssignedForeignEdwardsPoint<F, C, B>, Error> {
1052        if scalar == C::ScalarField::ZERO {
1053            return self.assign_fixed(layouter, C::CryptographicGroup::identity());
1054        } else if scalar == C::ScalarField::ONE {
1055            return Ok(base.clone());
1056        }
1057
1058        // If the base is a known constant, compute off-circuit.
1059        if let Some(base_val) = self.as_known_constant(base) {
1060            return self.assign_fixed(layouter, base_val * scalar);
1061        }
1062
1063        let scalar_bits = scalar.to_bits_le(None);
1064        let mut p = base.clone();
1065        let mut res = None;
1066
1067        // Simple double-and-add
1068        for (i, b) in scalar_bits.iter().enumerate() {
1069            if *b {
1070                res = match res {
1071                    None => Some(p.clone()),
1072                    Some(acc) => Some(self.add(layouter, &acc, &p)?),
1073                }
1074            }
1075            // The doubling in the last iteration is not needed
1076            if i + 1 < scalar_bits.len() {
1077                p = self.double(layouter, &p)?;
1078            }
1079        }
1080
1081        Ok(res.unwrap_or(self.assign_fixed(layouter, C::CryptographicGroup::identity())?))
1082    }
1083
1084    fn point_from_coordinates(
1085        &self,
1086        layouter: &mut impl Layouter<F>,
1087        x: &AssignedField<F, C::Base, B>,
1088        y: &AssignedField<F, C::Base, B>,
1089    ) -> Result<Self::Point, Error> {
1090        let p_value = x.value().zip(y.value()).map_with_result(|(x, y)| {
1091            C::from_xy(x, y)
1092                .map(|p| p.into_subgroup())
1093                .ok_or(Error::Synthesis("invalid coordinates".into()))
1094        })?;
1095
1096        let p = self.assign(layouter, p_value)?;
1097
1098        self.base_field_chip.assert_equal(layouter, x, &self.x_coordinate(&p))?;
1099        self.base_field_chip.assert_equal(layouter, y, &self.y_coordinate(&p))?;
1100
1101        Ok(p)
1102    }
1103
1104    fn x_coordinate(&self, point: &Self::Point) -> Self::Coordinate {
1105        point.x.clone()
1106    }
1107
1108    fn y_coordinate(&self, point: &Self::Point) -> Self::Coordinate {
1109        point.y.clone()
1110    }
1111
1112    fn base_field(&self) -> &impl DecompositionInstructions<F, Self::Coordinate> {
1113        self.base_field_chip()
1114    }
1115
1116    fn assign_without_subgroup_check(
1117        &self,
1118        layouter: &mut impl Layouter<F>,
1119        value: Value<C::CryptographicGroup>,
1120    ) -> Result<Self::Point, Error> {
1121        let p = self.assign_point_unchecked(layouter, value)?;
1122        self.assert_on_curve(layouter, &p.x, &p.y)?;
1123        Ok(p)
1124    }
1125}
1126
1127/// Precomputed table of points for windowed MSM.
1128#[derive(Clone, Debug)]
1129struct PrecomputedTable<F, C, B, const WS: usize>
1130where
1131    F: CircuitField,
1132    C: EdwardsCurve,
1133    B: FieldEmulationParams<F, C::Base>,
1134{
1135    /// Table of precomputed points, where `table[i] = i * base`.
1136    table: Vec<AssignedForeignEdwardsPoint<F, C, B>>,
1137}
1138
1139impl<F, C, B, S, N> ForeignEdwardsEccChip<F, C, B, S, N>
1140where
1141    F: CircuitField,
1142    C: EdwardsCurve,
1143    B: FieldEmulationParams<F, C::Base>,
1144    S: ScalarFieldInstructions<F>,
1145    S::Scalar: InnerValue<Element = C::ScalarField>,
1146    N: NativeInstructions<F>,
1147{
1148    /// Builds table `[0*base, 1*base, ..., (2^WS-1)*base]`.
1149    /// Uses doubling for even indices (`table[2k] = double(table[k])`) and
1150    /// addition for odd indices (`table[2k+1] = add(table[2k], base)`),
1151    /// which is cheaper than a linear chain of additions.
1152    fn precompute<const WS: usize>(
1153        &self,
1154        layouter: &mut impl Layouter<F>,
1155        base: &AssignedForeignEdwardsPoint<F, C, B>,
1156    ) -> Result<PrecomputedTable<F, C, B, WS>, Error> {
1157        let identity = self.assign_fixed(layouter, C::CryptographicGroup::identity())?;
1158        let mut table = vec![identity, base.clone()];
1159        for i in 2..1 << WS {
1160            let entry = if i % 2 == 0 {
1161                self.double(layouter, &table[i / 2])?
1162            } else {
1163                self.add(layouter, &table[i - 1], base)?
1164            };
1165            table.push(entry);
1166        }
1167        Ok(PrecomputedTable { table })
1168    }
1169
1170    /// Delegates to [`fill_dynamic_lookup_row`] with this chip's columns.
1171    #[allow(clippy::type_complexity)]
1172    fn fill_dynamic_lookup_row(
1173        &self,
1174        layouter: &mut impl Layouter<F>,
1175        point: &AssignedForeignEdwardsPoint<F, C, B>,
1176        index: &AssignedNative<F>,
1177        table_tag: F,
1178        enable_lookup: bool,
1179    ) -> Result<(Vec<AssignedNative<F>>, Vec<AssignedNative<F>>), Error> {
1180        fill_dynamic_lookup_row(
1181            layouter,
1182            &point.x.limb_values(),
1183            &point.y.limb_values(),
1184            index,
1185            &self.config.base_field_config.x_cols,
1186            &self.config.base_field_config.z_cols, // z_cols used for y (y_cols == x_cols)
1187            self.config.idx_col_multi_select,
1188            self.config.tag_col_multi_select,
1189            self.config.q_multi_select,
1190            table_tag,
1191            enable_lookup,
1192        )
1193    }
1194
1195    /// Loads a precomputed point table into the dynamic lookup.  Entry `i` is
1196    /// paired with index `i` and the given `table_tag`.
1197    fn load_multi_select_table(
1198        &self,
1199        layouter: &mut impl Layouter<F>,
1200        point_table: &[AssignedForeignEdwardsPoint<F, C, B>],
1201        table_tag: F,
1202    ) -> Result<(), Error> {
1203        for (i, point) in point_table.iter().enumerate() {
1204            let index = self.native_gadget.assign_fixed(layouter, F::from(i as u64))?;
1205            self.fill_dynamic_lookup_row(layouter, point, &index, table_tag, false)?;
1206        }
1207        Ok(())
1208    }
1209
1210    /// Returns `point_table[selector]` using the dynamic lookup.
1211    ///
1212    /// The table must have been loaded via [`Self::load_multi_select_table`]
1213    /// with the same `table_tag`.
1214    fn multi_select(
1215        &self,
1216        layouter: &mut impl Layouter<F>,
1217        selector: &AssignedNative<F>,
1218        point_table: &[AssignedForeignEdwardsPoint<F, C, B>],
1219        table_tag: F,
1220    ) -> Result<AssignedForeignEdwardsPoint<F, C, B>, Error> {
1221        let mut selector_idx = 0usize;
1222        selector.value().map(|v| {
1223            let digits = v.to_biguint().to_u32_digits();
1224            let digit = if digits.is_empty() { 0 } else { digits[0] };
1225            debug_assert!(digits.len() <= 1);
1226            debug_assert!((digit as usize) < point_table.len());
1227            selector_idx = digit as usize;
1228        });
1229
1230        let selected = point_table[selector_idx].clone();
1231
1232        let (xs, ys) =
1233            self.fill_dynamic_lookup_row(layouter, &selected, selector, table_tag, true)?;
1234        let x = AssignedField::<F, C::Base, B>::from_limbs_unsafe(xs);
1235        let y = AssignedField::<F, C::Base, B>::from_limbs_unsafe(ys);
1236
1237        Ok(AssignedForeignEdwardsPoint::<F, C, B> {
1238            point: selected.point,
1239            x,
1240            y,
1241        })
1242    }
1243
1244    /// Windowed interleaved MSM over pre-chunked scalars. Each scalar is a
1245    /// sequence of WS-bit window values (native field elements). Shares the
1246    /// doubling chain across all bases. Horner evaluation. Scalars may have
1247    /// different numbers of windows; short scalars are skipped in high windows.
1248    /// Point selection uses a dynamic-lookup table.
1249    fn windowed_msm<const WS: usize>(
1250        &self,
1251        layouter: &mut impl Layouter<F>,
1252        scalars: &[Vec<AssignedNative<F>>],
1253        bases: &[AssignedForeignEdwardsPoint<F, C, B>],
1254    ) -> Result<AssignedForeignEdwardsPoint<F, C, B>, Error> {
1255        assert_eq!(scalars.len(), bases.len());
1256
1257        if bases.is_empty() {
1258            return self.assign_fixed(layouter, C::CryptographicGroup::identity());
1259        }
1260
1261        // Number of windows per scalar.
1262        let num_windows: Vec<usize> = scalars.iter().map(|s| s.len()).collect();
1263        let max_num_windows = *num_windows.iter().max().unwrap();
1264
1265        // Precompute tables for each base and load them into the dynamic lookup.
1266        let tag_cnt = *self.tag_cnt.borrow();
1267        self.tag_cnt.replace(tag_cnt + bases.len() as u64);
1268        debug_assert!(F::NUM_BITS > 64);
1269
1270        let mut tables = vec![];
1271        for (i, base) in bases.iter().enumerate() {
1272            let table = self.precompute::<WS>(layouter, base)?;
1273            self.load_multi_select_table(layouter, &table.table, F::from(tag_cnt + i as u64))?;
1274            tables.push(table);
1275        }
1276
1277        let mut res = self.assign_fixed(layouter, C::CryptographicGroup::identity())?;
1278        for w in (0..max_num_windows).rev() {
1279            // Skip doubling in the most-significant window.
1280            if w < max_num_windows - 1 {
1281                for _ in 0..WS {
1282                    res = self.double(layouter, &res)?;
1283                }
1284            }
1285            for (i, (windows, nw)) in scalars.iter().zip(&num_windows).enumerate() {
1286                // Skip scalars that have no windows in this position.
1287                if w >= *nw {
1288                    continue;
1289                }
1290                let addend = self.multi_select(
1291                    layouter,
1292                    &windows[w],
1293                    &tables[i].table,
1294                    F::from(tag_cnt + i as u64),
1295                )?;
1296                res = self.add(layouter, &res, &addend)?;
1297            }
1298        }
1299
1300        Ok(res)
1301    }
1302}
1303
1304#[derive(Clone, Debug)]
1305#[cfg(any(test, feature = "testing"))]
1306/// Configuration used to implement `FromScratch` for the
1307/// `ForeignEdwardsEccChip` chip. This should only be used for testing.
1308pub struct ForeignEdwardsEccTestConfig<F, C, S, N>
1309where
1310    F: CircuitField,
1311    C: EdwardsCurve,
1312    S: ScalarFieldInstructions<F> + FromScratch<F>,
1313    S::Scalar: InnerValue<Element = C::ScalarField>,
1314    N: NativeInstructions<F> + FromScratch<F>,
1315{
1316    native_gadget_config: <N as FromScratch<F>>::Config,
1317    scalar_field_config: <S as FromScratch<F>>::Config,
1318    ff_ecc_config: ForeignEdwardsEccConfig<C>,
1319}
1320
1321#[cfg(any(test, feature = "testing"))]
1322impl<F, C, B, S, N> FromScratch<F> for ForeignEdwardsEccChip<F, C, B, S, N>
1323where
1324    F: CircuitField,
1325    C: EdwardsCurve,
1326    C::Base: Legendre,
1327    B: FieldEmulationParams<F, C::Base>,
1328    S: ScalarFieldInstructions<F> + FromScratch<F>,
1329    S::Scalar: InnerValue<Element = C::ScalarField>,
1330    N: NativeInstructions<F> + FromScratch<F>,
1331{
1332    type Config = ForeignEdwardsEccTestConfig<F, C, S, N>;
1333
1334    fn new_from_scratch(config: &Self::Config) -> Self {
1335        let native_gadget = <N as FromScratch<F>>::new_from_scratch(&config.native_gadget_config);
1336        let scalar_field_chip =
1337            <S as FromScratch<F>>::new_from_scratch(&config.scalar_field_config);
1338        ForeignEdwardsEccChip::new(&config.ff_ecc_config, &native_gadget, &scalar_field_chip)
1339    }
1340
1341    fn load_from_scratch(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
1342        self.native_gadget.load_from_scratch(layouter)?;
1343        self.scalar_field_chip.load_from_scratch(layouter)
1344    }
1345
1346    fn configure_from_scratch(
1347        meta: &mut ConstraintSystem<F>,
1348        advice_columns: &mut Vec<Column<Advice>>,
1349        fixed_columns: &mut Vec<Column<Fixed>>,
1350        instance_columns: &[Column<Instance>; 2],
1351    ) -> ForeignEdwardsEccTestConfig<F, C, S, N> {
1352        use crate::field::foreign::nb_field_chip_columns;
1353
1354        let native_gadget_config = <N as FromScratch<F>>::configure_from_scratch(
1355            meta,
1356            advice_columns,
1357            fixed_columns,
1358            instance_columns,
1359        );
1360        let scalar_field_config = <S as FromScratch<F>>::configure_from_scratch(
1361            meta,
1362            advice_columns,
1363            fixed_columns,
1364            instance_columns,
1365        );
1366        let nb_advice_cols = nb_field_chip_columns::<F, C::Base, B>();
1367        while advice_columns.len() < nb_advice_cols {
1368            advice_columns.push(meta.advice_column());
1369        }
1370        let nb_parallel_range_checks = 4;
1371        let max_bit_len = 8;
1372        let base_field_config = FieldChip::<F, C::Base, B, N>::configure(
1373            meta,
1374            &advice_columns[..nb_advice_cols],
1375            nb_parallel_range_checks,
1376            max_bit_len,
1377        );
1378        let ff_ecc_config = ForeignEdwardsEccChip::<F, C, B, S, N>::configure(
1379            meta,
1380            &base_field_config,
1381            advice_columns,
1382            fixed_columns,
1383            nb_parallel_range_checks,
1384            max_bit_len,
1385        );
1386        ForeignEdwardsEccTestConfig {
1387            native_gadget_config,
1388            scalar_field_config,
1389            ff_ecc_config,
1390        }
1391    }
1392}
1393
1394#[cfg(test)]
1395mod tests {
1396    use ff::Field;
1397    use group::{Group, GroupEncoding};
1398    use midnight_curves::{curve25519::Curve25519, BlsScalar};
1399    use midnight_proofs::{circuit::SimpleFloorPlanner, dev::MockProver, plonk::Circuit};
1400    use rand::SeedableRng;
1401    use rand_chacha::ChaCha8Rng;
1402
1403    use super::*;
1404    use crate::{
1405        ecc::curves::CircuitCurve,
1406        field::{
1407            decomposition::chip::P2RDecompositionChip, foreign::params::MultiEmulationParams,
1408            NativeChip, NativeGadget,
1409        },
1410        instructions::{assertions, control_flow, ecc, equality, public_input, zero},
1411    };
1412
1413    type F = BlsScalar;
1414    type Native<F> = NativeGadget<F, P2RDecompositionChip<F>, NativeChip<F>>;
1415    type EmulatedField<F, C> = FieldChip<F, <C as Group>::Scalar, MultiEmulationParams, Native<F>>;
1416
1417    macro_rules! test_generic {
1418        ($mod:ident, $op:ident, $native:ty, $curve:ty, $scalar_field:ty,
1419    $name:expr) => {
1420            $mod::tests::$op::<
1421                $native,
1422                AssignedForeignEdwardsPoint<$native, $curve, MultiEmulationParams>,
1423                ForeignEdwardsEccChip<
1424                    $native,
1425                    $curve,
1426                    MultiEmulationParams,
1427                    $scalar_field,
1428                    Native<$native>,
1429                >,
1430            >($name);
1431        };
1432    }
1433
1434    macro_rules! test {
1435        ($mod:ident, $op:ident) => {
1436            #[test]
1437            fn $op() {
1438                test_generic!($mod, $op, F, Curve25519, EmulatedField<F, Curve25519>, "emulated_curve25519");
1439            }
1440        };
1441    }
1442
1443    test!(assertions, test_assertions);
1444
1445    test!(public_input, test_public_inputs);
1446
1447    test!(equality, test_is_equal);
1448
1449    test!(zero, test_zero_assertions);
1450    test!(zero, test_is_zero);
1451
1452    test!(control_flow, test_select);
1453    test!(control_flow, test_cond_assert_equal);
1454    test!(control_flow, test_cond_swap);
1455
1456    macro_rules! ecc_test {
1457        ($op:ident, $native:ty, $curve:ty, $scalar_field:ty, $name:expr) => {
1458            ecc::tests::$op::<
1459                $native,
1460                $curve,
1461                ForeignEdwardsEccChip<
1462                    $native,
1463                    $curve,
1464                    MultiEmulationParams,
1465                    $scalar_field,
1466                    Native<$native>,
1467                >,
1468            >($name);
1469        };
1470    }
1471
1472    macro_rules! ecc_tests {
1473        ($op:ident) => {
1474            #[test]
1475            fn $op() {
1476                ecc_test!($op, BlsScalar, Curve25519, EmulatedField<BlsScalar, Curve25519>, "emulated_curve25519");
1477            }
1478        };
1479    }
1480
1481    ecc_tests!(test_assign);
1482    ecc_tests!(test_assign_without_subgroup_check);
1483    ecc_tests!(test_add);
1484    ecc_tests!(test_double);
1485    ecc_tests!(test_negate);
1486    ecc_tests!(test_msm);
1487    ecc_tests!(test_msm_by_bounded_scalars);
1488    ecc_tests!(test_mul_by_constant);
1489    ecc_tests!(test_coordinates);
1490
1491    #[test]
1492    fn test_assert_on_curve() {
1493        run_test_assert_on_curve::<Curve25519>();
1494    }
1495
1496    /// Negative tests for `assert_on_curve`. Positive cases (identity,
1497    /// generator, random points) are covered by the generic `test_assign`.
1498    fn run_test_assert_on_curve<C>()
1499    where
1500        C: EdwardsCurve,
1501        C::Base: Legendre,
1502        MultiEmulationParams: FieldEmulationParams<BlsScalar, C::Base>
1503            + FieldEmulationParams<BlsScalar, C::ScalarField>,
1504    {
1505        fn assert_not_on_curve<C: EdwardsCurve>(x: C::Base, y: C::Base)
1506        where
1507            C::Base: Legendre,
1508            MultiEmulationParams: FieldEmulationParams<BlsScalar, C::Base>
1509                + FieldEmulationParams<BlsScalar, C::ScalarField>,
1510        {
1511            let circuit = OnCurveCheckCircuit::<C> { x, y };
1512            let prover = MockProver::run(&circuit, vec![vec![], vec![]])
1513                .expect("proof generation should not fail");
1514            assert!(prover.verify().is_err());
1515        }
1516
1517        let mut rng = ChaCha8Rng::seed_from_u64(0x0);
1518
1519        // Random point with y offset by 1
1520        let point = C::CryptographicGroup::random(&mut rng);
1521        let (x, y) = point.into().coordinates().expect("valid curve point");
1522
1523        assert_not_on_curve::<C>(x, y + C::Base::ONE);
1524        assert_not_on_curve::<C>(C::Base::ONE, C::Base::ONE);
1525        assert_not_on_curve::<C>(C::Base::ZERO, C::Base::ZERO);
1526    }
1527
1528    type EdwardsChip<C> = ForeignEdwardsEccChip<
1529        F,
1530        C,
1531        MultiEmulationParams,
1532        FieldChip<F, <C as CircuitCurve>::ScalarField, MultiEmulationParams, Native<F>>,
1533        Native<F>,
1534    >;
1535
1536    /// Test circuit that calls `assert_on_curve` for arbitrary (x, y)
1537    /// coordinates (not necessarily representing a valid curve point).
1538    ///
1539    /// This circuit checks if `assert_on_curve` correctly verifies, or fails,
1540    /// on a selected set of inputs.
1541    #[derive(Clone, Debug)]
1542    struct OnCurveCheckCircuit<C: EdwardsCurve> {
1543        x: C::Base,
1544        y: C::Base,
1545    }
1546
1547    impl<C> Circuit<F> for OnCurveCheckCircuit<C>
1548    where
1549        C: EdwardsCurve,
1550        C::Base: Legendre,
1551        MultiEmulationParams:
1552            FieldEmulationParams<F, C::Base> + FieldEmulationParams<F, C::ScalarField>,
1553    {
1554        type Config = <EdwardsChip<C> as FromScratch<F>>::Config;
1555        type FloorPlanner = SimpleFloorPlanner;
1556        type Params = ();
1557
1558        fn without_witnesses(&self) -> Self {
1559            unreachable!()
1560        }
1561
1562        fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
1563            let committed = meta.instance_column();
1564            let instance = meta.instance_column();
1565            EdwardsChip::<C>::configure_from_scratch(
1566                meta,
1567                &mut vec![],
1568                &mut vec![],
1569                &[committed, instance],
1570            )
1571        }
1572
1573        fn synthesize(
1574            &self,
1575            config: Self::Config,
1576            mut layouter: impl Layouter<F>,
1577        ) -> Result<(), Error> {
1578            let chip = EdwardsChip::<C>::new_from_scratch(&config);
1579
1580            let x = chip.base_field_chip().assign(&mut layouter, Value::known(self.x))?;
1581            let y = chip.base_field_chip().assign(&mut layouter, Value::known(self.y))?;
1582
1583            chip.assert_on_curve(&mut layouter, &x, &y)?;
1584            chip.load_from_scratch(&mut layouter)
1585        }
1586    }
1587
1588    /// Test circuit that calls `from_canonical_compressed_bytes` on a
1589    /// given byte array together with the claimed subgroup point.
1590    ///
1591    /// The proof succeeds if and only if the byte array is the canonical
1592    /// encoding of the subgroup point.
1593    #[derive(Clone, Debug)]
1594    struct FromCompressedBytesCheckCircuit {
1595        point: Curve25519Subgroup,
1596        bytes: [u8; 32],
1597    }
1598
1599    impl Circuit<F> for FromCompressedBytesCheckCircuit {
1600        type Config = <EdwardsChip<Curve25519> as FromScratch<F>>::Config;
1601        type FloorPlanner = SimpleFloorPlanner;
1602        type Params = ();
1603
1604        fn without_witnesses(&self) -> Self {
1605            unreachable!()
1606        }
1607
1608        fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
1609            let committed = meta.instance_column();
1610            let instance = meta.instance_column();
1611            EdwardsChip::<Curve25519>::configure_from_scratch(
1612                meta,
1613                &mut vec![],
1614                &mut vec![],
1615                &[committed, instance],
1616            )
1617        }
1618
1619        fn synthesize(
1620            &self,
1621            config: Self::Config,
1622            mut layouter: impl Layouter<F>,
1623        ) -> Result<(), Error> {
1624            let chip = EdwardsChip::<Curve25519>::new_from_scratch(&config);
1625
1626            let byte_cells: [AssignedByte<F>; 32] = self
1627                .bytes
1628                .iter()
1629                .map(|b| chip.native_gadget.assign(&mut layouter, Value::known(*b)))
1630                .collect::<Result<Vec<_>, _>>()?
1631                .try_into()
1632                .expect("exactly 32 bytes");
1633
1634            let _ = chip.from_canonical_compressed_bytes(
1635                &mut layouter,
1636                &byte_cells,
1637                Value::known(self.point),
1638            )?;
1639
1640            chip.load_from_scratch(&mut layouter)
1641        }
1642    }
1643
1644    fn run_test_compressed_bytes(point: Curve25519Subgroup, bytes: [u8; 32], should_accept: bool) {
1645        let circuit = FromCompressedBytesCheckCircuit { point, bytes };
1646        let prover = MockProver::run(&circuit, vec![vec![], vec![]])
1647            .expect("proof generation should not fail");
1648        assert_eq!(prover.verify().is_ok(), should_accept);
1649    }
1650
1651    #[test]
1652    fn test_compressed_bytes() {
1653        // Canonical LE encoding of the identity with y = 1 and sign_x = 0.
1654        let mut canonical = [0; 32];
1655        canonical[0] = 1;
1656        run_test_compressed_bytes(Curve25519Subgroup::identity(), canonical, true);
1657
1658        // Non-canonical LE encoding of the identity with y = 2^255 - 18 and sign_x = 0.
1659        let mut non_canonical = [0xff_u8; 32];
1660        non_canonical[0] = 0xee;
1661        non_canonical[31] = 0x7f;
1662        run_test_compressed_bytes(Curve25519Subgroup::identity(), non_canonical, false);
1663
1664        // Non-canonical LE encoding of the identity with y = 1 and sign_x = 1.
1665        let mut non_canonical_with_sign = canonical;
1666        non_canonical_with_sign[31] = 0x80;
1667        run_test_compressed_bytes(
1668            Curve25519Subgroup::identity(),
1669            non_canonical_with_sign,
1670            false,
1671        );
1672
1673        // Canonical LE encoding of the subgroup generator.
1674        let g = Curve25519Subgroup::generator();
1675        run_test_compressed_bytes(g, Curve25519::from(g).to_bytes(), true);
1676
1677        // Canonical LE encoding of a random subgroup point.
1678        let mut rng = ChaCha8Rng::seed_from_u64(0x7374727564656C);
1679        let p = Curve25519Subgroup::random(&mut rng);
1680        run_test_compressed_bytes(p, Curve25519::from(p).to_bytes(), true);
1681    }
1682}