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
368impl<F, C, B> InnerValue for AssignedForeignEdwardsPoint<F, C, B>
369where
370    F: CircuitField,
371    C: EdwardsCurve,
372    B: FieldEmulationParams<F, C::Base>,
373{
374    type Element = C::CryptographicGroup;
375
376    fn value(&self) -> Value<Self::Element> {
377        self.point
378    }
379}
380
381impl<F, C, B> InnerConstants for AssignedForeignEdwardsPoint<F, C, B>
382where
383    F: CircuitField,
384    C: EdwardsCurve,
385    B: FieldEmulationParams<F, C::Base>,
386{
387    fn inner_zero() -> C::CryptographicGroup {
388        C::CryptographicGroup::identity()
389    }
390
391    fn inner_one() -> Self::Element {
392        C::CryptographicGroup::generator()
393    }
394}
395
396#[cfg(any(test, feature = "testing"))]
397impl<F, C, B> Sampleable for AssignedForeignEdwardsPoint<F, C, B>
398where
399    F: CircuitField,
400    C: EdwardsCurve,
401    B: FieldEmulationParams<F, C::Base>,
402{
403    fn sample_inner(rng: impl RngCore) -> C::CryptographicGroup {
404        C::CryptographicGroup::random(rng)
405    }
406}
407
408impl<F, C, B, S, N> Chip<F> for ForeignEdwardsEccChip<F, C, B, S, N>
409where
410    F: CircuitField,
411    C: EdwardsCurve,
412    B: FieldEmulationParams<F, C::Base>,
413    S: ScalarFieldInstructions<F>,
414    S::Scalar: InnerValue<Element = C::ScalarField>,
415    N: NativeInstructions<F>,
416{
417    type Config = ForeignEdwardsEccConfig<C>;
418    type Loaded = ();
419    fn config(&self) -> &Self::Config {
420        &self.config
421    }
422    fn loaded(&self) -> &Self::Loaded {
423        &()
424    }
425}
426
427impl<F, C, B, S, N> ForeignEdwardsEccChip<F, C, B, S, N>
428where
429    F: CircuitField,
430    C: EdwardsCurve,
431    B: FieldEmulationParams<F, C::Base>,
432    S: ScalarFieldInstructions<F>,
433    S::Scalar: InnerValue<Element = C::ScalarField>,
434    N: NativeInstructions<F>,
435{
436    /// Converts a subgroup point to [AssignedForeignEdwardsPoint].
437    /// The point is _not_ asserted (with constraints) to be on the curve.
438    fn assign_point_unchecked(
439        &self,
440        layouter: &mut impl Layouter<F>,
441        value: Value<C::CryptographicGroup>,
442    ) -> Result<AssignedForeignEdwardsPoint<F, C, B>, Error> {
443        let (val_x, val_y) = value
444            .map(|v| v.into().coordinates().expect("Edwards coordinates cannot fail"))
445            .unzip();
446        let x = self.base_field_chip().assign(layouter, val_x)?;
447        let y = self.base_field_chip().assign(layouter, val_y)?;
448        let p = AssignedForeignEdwardsPoint::<F, C, B> { point: value, x, y };
449
450        Ok(p)
451    }
452
453    /// Asserts the curve equation `a*x^2 + y^2 = 1 + d*x^2*y^2` of an emulated
454    /// twisted Edwards curve, given the x and y coordinates in form of
455    /// [AssignedField].
456    fn assert_on_curve(
457        &self,
458        layouter: &mut impl Layouter<F>,
459        x: &AssignedField<F, C::Base, B>,
460        y: &AssignedField<F, C::Base, B>,
461    ) -> Result<(), Error> {
462        let base_chip = self.base_field_chip();
463
464        // Compute x^2, y^2 and a*x^2 + y^2 - 1 - d*x^2*y^2 in-circuit
465        let x_sq = base_chip.mul(layouter, x, x, None)?;
466        let y_sq = base_chip.mul(layouter, y, y, None)?;
467        let d_xy_sq = base_chip.mul(layouter, &x_sq, &y_sq, Some(C::D))?;
468        let lhs = base_chip.linear_combination(
469            layouter,
470            &[(C::A, x_sq), (C::Base::ONE, y_sq)],
471            -C::Base::ONE,
472        )?;
473
474        // Assert a*x^2 + y^2 - 1 = d*x^2*y^2
475        base_chip.assert_equal(layouter, &lhs, &d_xy_sq)
476    }
477
478    /// Adds an assigned point `p` to a constant point `q_val`. Cheaper than
479    /// general `add` because the constant coordinates turn emulated `mul`
480    /// calls into `mul_by_constant` calls.
481    fn add_constant(
482        &self,
483        layouter: &mut impl Layouter<F>,
484        p: &AssignedForeignEdwardsPoint<F, C, B>,
485        q_val: C::CryptographicGroup,
486    ) -> Result<AssignedForeignEdwardsPoint<F, C, B>, Error> {
487        // If both operands are known constants, compute off-circuit.
488        if let Some(pv) = self.as_known_constant(p) {
489            return self.assign_fixed(layouter, pv + q_val);
490        }
491
492        let (qx, qy) = q_val.into().coordinates().expect("Edwards coordinates cannot fail");
493
494        let base_chip = self.base_field_chip();
495
496        let r_value = p.value().map(|pv| pv + q_val);
497        let r = self.assign_point_unchecked(layouter, r_value)?;
498
499        let px_qx = base_chip.mul_by_constant(layouter, &p.x, qx)?;
500        let py_qy = base_chip.mul_by_constant(layouter, &p.y, qy)?;
501        let px_qy = base_chip.mul_by_constant(layouter, &p.x, qy)?;
502        let py_qx = base_chip.mul_by_constant(layouter, &p.y, qx)?;
503        let neg_a_px_qx = base_chip.mul_by_constant(layouter, &px_qx, -C::A)?;
504        let d_px_py_qx_qy = base_chip.mul(layouter, &px_qx, &py_qy, Some(C::D))?;
505
506        // Rx * (1 + d * Px * Py * Qx * Qy) = (Px * Qy + Py * Qx)
507        addition::assert_addition_coordinate(
508            layouter,
509            &r.x,
510            &px_qy,
511            &py_qx,
512            &d_px_py_qx_qy,
513            false,
514            base_chip,
515            &self.config.addition_config,
516        )?;
517
518        // Ry * (1 - d * Px * Py * Qx * Qy) = (Py * Qy - a * Px * Qx)
519        addition::assert_addition_coordinate(
520            layouter,
521            &r.y,
522            &py_qy,
523            &neg_a_px_qx,
524            &d_px_py_qx_qy,
525            true,
526            base_chip,
527            &self.config.addition_config,
528        )?;
529
530        Ok(AssignedForeignEdwardsPoint {
531            point: r_value,
532            x: r.x,
533            y: r.y,
534        })
535    }
536}
537
538impl<F, C, B, S, N> AssignmentInstructions<F, AssignedForeignEdwardsPoint<F, C, B>>
539    for ForeignEdwardsEccChip<F, C, B, S, N>
540where
541    F: CircuitField,
542    C: EdwardsCurve,
543    B: FieldEmulationParams<F, C::Base>,
544    S: ScalarFieldInstructions<F>,
545    S::Scalar: InnerValue<Element = C::ScalarField>,
546    N: NativeInstructions<F>,
547{
548    fn assign(
549        &self,
550        layouter: &mut impl Layouter<F>,
551        p_value: Value<C::CryptographicGroup>,
552    ) -> Result<AssignedForeignEdwardsPoint<F, C, B>, Error> {
553        // Let h be the cofactor of the subgroup.
554        //
555        // Instead of witnessing P, we witness an h-root Q, and return h * Q.
556        // This guarantess that the returned point is in the desired subgroup.
557        let cofactor = C::ScalarField::from_u128(C::COFACTOR);
558        let q =
559            self.assign_point_unchecked(layouter, p_value.map(|p| p * cofactor.invert().unwrap()))?;
560
561        self.assert_on_curve(layouter, &q.x, &q.y)?;
562        self.mul_by_constant(layouter, cofactor, &q)
563    }
564
565    fn assign_fixed(
566        &self,
567        layouter: &mut impl Layouter<F>,
568        constant: C::CryptographicGroup,
569    ) -> Result<AssignedForeignEdwardsPoint<F, C, B>, Error> {
570        let (x, y) = constant.into().coordinates().expect("Edwards coordinates cannot fail");
571        let x = self.base_field_chip().assign_fixed(layouter, x)?;
572        let y = self.base_field_chip().assign_fixed(layouter, y)?;
573
574        let p = AssignedForeignEdwardsPoint::<F, C, B> {
575            point: Value::known(constant),
576            x,
577            y,
578        };
579        self.constant_cache.borrow_mut().insert(p.clone(), constant);
580        Ok(p)
581    }
582}
583
584impl<F, C, B, S, N> PublicInputInstructions<F, AssignedForeignEdwardsPoint<F, C, B>>
585    for ForeignEdwardsEccChip<F, C, B, S, N>
586where
587    F: CircuitField,
588    C: EdwardsCurve,
589    B: FieldEmulationParams<F, C::Base>,
590    S: ScalarFieldInstructions<F>,
591    S::Scalar: InnerValue<Element = C::ScalarField>,
592    N: NativeInstructions<F> + PublicInputInstructions<F, AssignedBit<F>>,
593{
594    fn as_public_input(
595        &self,
596        layouter: &mut impl Layouter<F>,
597        p: &AssignedForeignEdwardsPoint<F, C, B>,
598    ) -> Result<Vec<AssignedNative<F>>, Error> {
599        Ok([
600            self.base_field_chip.as_public_input(layouter, &p.x)?.as_slice(),
601            self.base_field_chip.as_public_input(layouter, &p.y)?.as_slice(),
602        ]
603        .concat())
604    }
605
606    fn constrain_as_public_input(
607        &self,
608        layouter: &mut impl Layouter<F>,
609        assigned: &AssignedForeignEdwardsPoint<F, C, B>,
610    ) -> Result<(), Error> {
611        self.as_public_input(layouter, assigned)?
612            .iter()
613            .try_for_each(|c| self.native_gadget.constrain_as_public_input(layouter, c))
614    }
615
616    fn assign_as_public_input(
617        &self,
618        layouter: &mut impl Layouter<F>,
619        value: Value<C::CryptographicGroup>,
620    ) -> Result<AssignedForeignEdwardsPoint<F, C, B>, Error> {
621        let point = self.assign(layouter, value)?;
622        self.constrain_as_public_input(layouter, &point)?;
623        Ok(point)
624    }
625}
626
627/// Inherit assignment instructions for [AssignedField], from the
628/// `scalar_field_chip`.
629impl<F, C, B, S, SP, N> AssignmentInstructions<F, AssignedField<F, C::ScalarField, SP>>
630    for ForeignEdwardsEccChip<F, C, B, S, N>
631where
632    F: CircuitField,
633    C: EdwardsCurve,
634    B: FieldEmulationParams<F, C::Base>,
635    S: ScalarFieldInstructions<F, Scalar = AssignedField<F, C::ScalarField, SP>>,
636    SP: FieldEmulationParams<F, C::ScalarField>,
637    N: NativeInstructions<F>,
638{
639    fn assign(
640        &self,
641        layouter: &mut impl Layouter<F>,
642        value: Value<<S::Scalar as InnerValue>::Element>,
643    ) -> Result<S::Scalar, Error> {
644        self.scalar_field_chip().assign(layouter, value)
645    }
646
647    fn assign_fixed(
648        &self,
649        layouter: &mut impl Layouter<F>,
650        constant: <S::Scalar as InnerValue>::Element,
651    ) -> Result<S::Scalar, Error> {
652        self.scalar_field_chip().assign_fixed(layouter, constant)
653    }
654}
655
656impl<F, C, B, S, N> AssertionInstructions<F, AssignedForeignEdwardsPoint<F, C, B>>
657    for ForeignEdwardsEccChip<F, C, B, S, N>
658where
659    F: CircuitField,
660    C: EdwardsCurve,
661    B: FieldEmulationParams<F, C::Base>,
662    S: ScalarFieldInstructions<F>,
663    S::Scalar: InnerValue<Element = C::ScalarField>,
664    N: NativeInstructions<F>,
665{
666    fn assert_equal(
667        &self,
668        layouter: &mut impl Layouter<F>,
669        p: &AssignedForeignEdwardsPoint<F, C, B>,
670        q: &AssignedForeignEdwardsPoint<F, C, B>,
671    ) -> Result<(), Error> {
672        self.base_field_chip().assert_equal(layouter, &p.x, &q.x)?;
673        self.base_field_chip().assert_equal(layouter, &p.y, &q.y)
674    }
675
676    fn assert_not_equal(
677        &self,
678        layouter: &mut impl Layouter<F>,
679        p: &AssignedForeignEdwardsPoint<F, C, B>,
680        q: &AssignedForeignEdwardsPoint<F, C, B>,
681    ) -> Result<(), Error> {
682        let p_eq_q = self.is_equal(layouter, p, q)?;
683        self.native_gadget.assert_equal_to_fixed(layouter, &p_eq_q, false)
684    }
685
686    fn assert_equal_to_fixed(
687        &self,
688        layouter: &mut impl Layouter<F>,
689        p: &AssignedForeignEdwardsPoint<F, C, B>,
690        constant: C::CryptographicGroup,
691    ) -> Result<(), Error> {
692        let coordinates = constant.into().coordinates().expect("Edwards coordinates cannot fail");
693        self.base_field_chip().assert_equal_to_fixed(layouter, &p.x, coordinates.0)?;
694        self.base_field_chip().assert_equal_to_fixed(layouter, &p.y, coordinates.1)
695    }
696
697    fn assert_not_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 p_eq_constant = self.is_equal_to_fixed(layouter, p, constant)?;
704        self.native_gadget.assert_equal_to_fixed(layouter, &p_eq_constant, false)
705    }
706}
707
708impl<F, C, B, S, N> EqualityInstructions<F, AssignedForeignEdwardsPoint<F, C, B>>
709    for ForeignEdwardsEccChip<F, C, B, S, N>
710where
711    F: CircuitField,
712    C: EdwardsCurve,
713    B: FieldEmulationParams<F, C::Base>,
714    S: ScalarFieldInstructions<F>,
715    S::Scalar: InnerValue<Element = C::ScalarField>,
716    N: NativeInstructions<F>,
717{
718    fn is_equal(
719        &self,
720        layouter: &mut impl Layouter<F>,
721        p: &AssignedForeignEdwardsPoint<F, C, B>,
722        q: &AssignedForeignEdwardsPoint<F, C, B>,
723    ) -> Result<AssignedBit<F>, Error> {
724        let eq_x = self.base_field_chip().is_equal(layouter, &p.x, &q.x)?;
725        let eq_y = self.base_field_chip().is_equal(layouter, &p.y, &q.y)?;
726        self.native_gadget.and(layouter, &[eq_x, eq_y])
727    }
728
729    fn is_equal_to_fixed(
730        &self,
731        layouter: &mut impl Layouter<F>,
732        p: &AssignedForeignEdwardsPoint<F, C, B>,
733        constant: <AssignedForeignEdwardsPoint<F, C, B> as InnerValue>::Element,
734    ) -> Result<AssignedBit<F>, Error> {
735        let coordinates = constant.into().coordinates().expect("Edwards coordinates cannot fail");
736        let eq_x = self.base_field_chip().is_equal_to_fixed(layouter, &p.x, coordinates.0)?;
737        let eq_y = self.base_field_chip().is_equal_to_fixed(layouter, &p.y, coordinates.1)?;
738        self.native_gadget.and(layouter, &[eq_x, eq_y])
739    }
740
741    fn is_not_equal(
742        &self,
743        layouter: &mut impl Layouter<F>,
744        x: &AssignedForeignEdwardsPoint<F, C, B>,
745        y: &AssignedForeignEdwardsPoint<F, C, B>,
746    ) -> Result<AssignedBit<F>, Error> {
747        let b = self.is_equal(layouter, x, y)?;
748        self.native_gadget.not(layouter, &b)
749    }
750
751    fn is_not_equal_to_fixed(
752        &self,
753        layouter: &mut impl Layouter<F>,
754        x: &AssignedForeignEdwardsPoint<F, C, B>,
755        constant: <AssignedForeignEdwardsPoint<F, C, B> as InnerValue>::Element,
756    ) -> Result<AssignedBit<F>, Error> {
757        let b = self.is_equal_to_fixed(layouter, x, constant)?;
758        self.native_gadget.not(layouter, &b)
759    }
760}
761
762impl<F, C, B, S, N> ZeroInstructions<F, AssignedForeignEdwardsPoint<F, C, B>>
763    for ForeignEdwardsEccChip<F, C, B, S, N>
764where
765    F: CircuitField,
766    C: EdwardsCurve,
767    B: FieldEmulationParams<F, C::Base>,
768    S: ScalarFieldInstructions<F>,
769    S::Scalar: InnerValue<Element = C::ScalarField>,
770    N: NativeInstructions<F>,
771{
772    fn is_zero(
773        &self,
774        layouter: &mut impl Layouter<F>,
775        p: &AssignedForeignEdwardsPoint<F, C, B>,
776    ) -> Result<AssignedBit<F>, Error> {
777        self.is_equal_to_fixed(layouter, p, C::CryptographicGroup::identity())
778    }
779}
780
781impl<F, C, B, S, N> ControlFlowInstructions<F, AssignedForeignEdwardsPoint<F, C, B>>
782    for ForeignEdwardsEccChip<F, C, B, S, N>
783where
784    F: CircuitField,
785    C: EdwardsCurve,
786    B: FieldEmulationParams<F, C::Base>,
787    S: ScalarFieldInstructions<F>,
788    S::Scalar: InnerValue<Element = C::ScalarField>,
789    N: NativeInstructions<F>,
790{
791    /// Returns `p` if `cond = 1` and `q` otherwise. In essence, this enforces
792    /// `cond * p + (1 - cond) * q = 0` over the emulated twisted Edwards curve.
793    fn select(
794        &self,
795        layouter: &mut impl Layouter<F>,
796        cond: &AssignedBit<F>,
797        p: &AssignedForeignEdwardsPoint<F, C, B>,
798        q: &AssignedForeignEdwardsPoint<F, C, B>,
799    ) -> Result<AssignedForeignEdwardsPoint<F, C, B>, Error> {
800        let point = p.point.zip(q.point).zip(cond.value()).map(|((p, q), b)| if b { p } else { q });
801        let x = self.base_field_chip().select(layouter, cond, &p.x, &q.x)?;
802        let y = self.base_field_chip().select(layouter, cond, &p.y, &q.y)?;
803        Ok(AssignedForeignEdwardsPoint::<F, C, B> { point, x, y })
804    }
805}
806
807impl<F, C, B, S, N> EccInstructions<F, C> for ForeignEdwardsEccChip<F, C, B, S, N>
808where
809    F: CircuitField,
810    C: EdwardsCurve,
811    B: FieldEmulationParams<F, C::Base>,
812    S: ScalarFieldInstructions<F>,
813    S::Scalar: InnerValue<Element = C::ScalarField>,
814    N: NativeInstructions<F>,
815{
816    type Point = AssignedForeignEdwardsPoint<F, C, B>;
817    type Coordinate = AssignedField<F, C::Base, B>;
818    type Scalar = S::Scalar;
819
820    fn add(
821        &self,
822        layouter: &mut impl Layouter<F>,
823        p: &Self::Point,
824        q: &Self::Point,
825    ) -> Result<Self::Point, Error> {
826        if p == q {
827            return self.double(layouter, p);
828        }
829
830        // If one operand is constant, use the cheaper `add_constant` path.
831        if let Some(qv) = self.as_known_constant(q) {
832            return self.add_constant(layouter, p, qv);
833        }
834        if let Some(pv) = self.as_known_constant(p) {
835            return self.add_constant(layouter, q, pv);
836        }
837
838        // Complete addition law on twisted Edwards curve:
839        // (see https://eprint.iacr.org/2008/013.pdf)
840        //
841        // P + Q = R
842        // <=>
843        // (Px, Py) + (Qx, Qy) = (Rx, Ry)
844        // <=>
845        // Rx = (Px * Qy +     Py * Qx) / (1 + d * Px * Py * Qx * Qy)
846        // Ry = (Py * Qy - a * Px * Qx) / (1 - d * Px * Py * Qx * Qy)
847        // <=> (denominators are non-zero)
848        // Rx * (1 + d * Px * Py * Qx * Qy) = (Px * Qy +     Py * Qx)
849        // Ry * (1 - d * Px * Py * Qx * Qy) = (Py * Qy - a * Px * Qx)
850
851        let base_chip = self.base_field_chip();
852
853        let r_value = p.value().zip(q.value()).map(|(p, q)| p + q);
854        let r = self.assign_point_unchecked(layouter, r_value)?;
855
856        let px_qx = base_chip.mul(layouter, &p.x, &q.x, None)?;
857        let py_qy = base_chip.mul(layouter, &p.y, &q.y, None)?;
858        let px_qy = base_chip.mul(layouter, &p.x, &q.y, None)?;
859        let py_qx = base_chip.mul(layouter, &p.y, &q.x, None)?;
860        let neg_a_px_qx = base_chip.mul_by_constant(layouter, &px_qx, -C::A)?;
861        let d_px_py_qx_qy = base_chip.mul(layouter, &px_qx, &py_qy, Some(C::D))?;
862
863        // Constraint for Rx coordinate
864        // Rx * (1 + d * Px * Py * Qx * Qy) = (Px * Qy + Py * Qx)
865        addition::assert_addition_coordinate(
866            layouter,
867            &r.x,
868            &px_qy,
869            &py_qx,
870            &d_px_py_qx_qy,
871            false,
872            base_chip,
873            &self.config.addition_config,
874        )?;
875
876        // Constraint for Ry coordinate
877        // Ry * (1 - d * Px * Py * Qx * Qy) = (Py * Qy - a * Px * Qx)
878        addition::assert_addition_coordinate(
879            layouter,
880            &r.y,
881            &py_qy,
882            &neg_a_px_qx,
883            &d_px_py_qx_qy,
884            true,
885            base_chip,
886            &self.config.addition_config,
887        )?;
888
889        Ok(AssignedForeignEdwardsPoint {
890            point: r_value,
891            x: r.x,
892            y: r.y,
893        })
894    }
895
896    fn double(
897        &self,
898        layouter: &mut impl Layouter<F>,
899        p: &AssignedForeignEdwardsPoint<F, C, B>,
900    ) -> Result<AssignedForeignEdwardsPoint<F, C, B>, Error> {
901        if let Some(pv) = self.as_known_constant(p) {
902            return self.assign_fixed(layouter, pv + pv);
903        }
904
905        // Complete doubling on twisted Edwards curve.
906        // (see https://eprint.iacr.org/2008/013.pdf)
907        //
908        // P + P = R
909        // <=>
910        // (Px, Py) + (Px, Py) = (Rx, Ry)
911        // <=>
912        // Rx = (Px * Py +     Py * Px) / (1 + d * Px * Py * Px * Py)
913        // Ry = (Py * Py - a * Px * Px) / (1 - d * Px * Py * Px * Py)
914        // <=> (denominators are non-zero)
915        // Rx * (1 + d * Px^2 * Py^2) = 2 * Px * Py
916        // Ry * (1 - d * Px^2 * Py^2) = Py^2 - a * Px^2
917        //
918        // Since P is on the curve: a * Px^2 + Py^2 = 1 + d * Px^2 * Py^2,
919        // we substitute w = a * Px^2 + Py^2 - 1 for d * Px^2 * Py^2,
920        // saving one emulated multiplication.
921
922        let base_chip = self.base_field_chip();
923
924        let r_value = p.value().map(|p| p + p);
925        let r = self.assign_point_unchecked(layouter, r_value)?;
926
927        let px_sq = base_chip.mul(layouter, &p.x, &p.x, None)?;
928        let py_sq = base_chip.mul(layouter, &p.y, &p.y, None)?;
929        let px_py = base_chip.mul(layouter, &p.x, &p.y, None)?;
930
931        let neg_a_px_sq = base_chip.mul_by_constant(layouter, &px_sq, -C::A)?;
932
933        // w = d * Px^2 * Py^2 = a * Px^2 + Py^2 - 1  (on-curve relation)
934        let w = base_chip.linear_combination(
935            layouter,
936            &[(C::A, px_sq), (C::Base::ONE, py_sq.clone())],
937            -C::Base::ONE,
938        )?;
939
940        // Rx * (1 + w) = 2 * Px * Py
941        addition::assert_addition_coordinate(
942            layouter,
943            &r.x,
944            &px_py,
945            &px_py,
946            &w,
947            false,
948            base_chip,
949            &self.config.addition_config,
950        )?;
951
952        // Ry * (1 - w) = Py^2 - a * Px^2
953        addition::assert_addition_coordinate(
954            layouter,
955            &r.y,
956            &py_sq,
957            &neg_a_px_sq,
958            &w,
959            true,
960            base_chip,
961            &self.config.addition_config,
962        )?;
963
964        Ok(AssignedForeignEdwardsPoint {
965            point: r_value,
966            x: r.x,
967            y: r.y,
968        })
969    }
970
971    fn negate(
972        &self,
973        layouter: &mut impl Layouter<F>,
974        p: &Self::Point,
975    ) -> Result<Self::Point, Error> {
976        if let Some(pv) = self.as_known_constant(p) {
977            return self.assign_fixed(layouter, -pv);
978        }
979
980        // The negation of `P = (x, y)` on a twisted Edwards curve is `-P = (-x, y)`
981        let neg_x = self.base_field_chip().neg(layouter, &p.x)?;
982        let neg_x = self.base_field_chip().normalize(layouter, &neg_x)?;
983        Ok(AssignedForeignEdwardsPoint::<F, C, B> {
984            point: -p.point,
985            x: neg_x,
986            y: p.y.clone(),
987        })
988    }
989
990    fn msm(
991        &self,
992        layouter: &mut impl Layouter<F>,
993        scalars: &[Self::Scalar],
994        bases: &[Self::Point],
995    ) -> Result<Self::Point, Error> {
996        let scalars = scalars
997            .iter()
998            .map(|s| (s.clone(), C::ScalarField::NUM_BITS as usize))
999            .collect::<Vec<_>>();
1000        self.msm_by_bounded_scalars(layouter, &scalars, bases)
1001    }
1002
1003    fn msm_by_bounded_scalars(
1004        &self,
1005        layouter: &mut impl Layouter<F>,
1006        scalars: &[(S::Scalar, usize)],
1007        bases: &[AssignedForeignEdwardsPoint<F, C, B>],
1008    ) -> Result<AssignedForeignEdwardsPoint<F, C, B>, Error> {
1009        if scalars.len() != bases.len() {
1010            panic!("Number of scalars and points should be the same.")
1011        }
1012        let scalar_chip = self.scalar_field_chip();
1013
1014        let (scalars, bases, bases_with_1bit_scalar) =
1015            msm_preprocess(self, scalar_chip, layouter, scalars, bases)?;
1016
1017        // Decompose scalars to bits with tight bound, then chunk into windows.
1018        const WS: usize = 4;
1019        let scalar_windows: Vec<Vec<AssignedNative<F>>> = scalars
1020            .iter()
1021            .map(|(s, num_bits)| {
1022                let bits = scalar_chip.assigned_to_le_bits(layouter, s, Some(*num_bits), true)?;
1023                bits.chunks(WS)
1024                    .map(|chunk| self.native_gadget.assigned_from_le_bits(layouter, chunk))
1025                    .collect::<Result<Vec<_>, _>>()
1026            })
1027            .collect::<Result<Vec<_>, _>>()?;
1028
1029        let res = self.windowed_msm::<WS>(layouter, &scalar_windows, &bases)?;
1030
1031        // Add 1-bit scalar bases.
1032        add_1bit_scalar_bases(layouter, self, scalar_chip, &bases_with_1bit_scalar, res)
1033    }
1034
1035    fn mul_by_constant(
1036        &self,
1037        layouter: &mut impl Layouter<F>,
1038        scalar: C::ScalarField,
1039        base: &AssignedForeignEdwardsPoint<F, C, B>,
1040    ) -> Result<AssignedForeignEdwardsPoint<F, C, B>, Error> {
1041        if scalar == C::ScalarField::ZERO {
1042            return self.assign_fixed(layouter, C::CryptographicGroup::identity());
1043        } else if scalar == C::ScalarField::ONE {
1044            return Ok(base.clone());
1045        }
1046
1047        // If the base is a known constant, compute off-circuit.
1048        if let Some(base_val) = self.as_known_constant(base) {
1049            return self.assign_fixed(layouter, base_val * scalar);
1050        }
1051
1052        let scalar_bits = scalar.to_bits_le(None);
1053        let mut p = base.clone();
1054        let mut res = None;
1055
1056        // Simple double-and-add
1057        for (i, b) in scalar_bits.iter().enumerate() {
1058            if *b {
1059                res = match res {
1060                    None => Some(p.clone()),
1061                    Some(acc) => Some(self.add(layouter, &acc, &p)?),
1062                }
1063            }
1064            // The doubling in the last iteration is not needed
1065            if i + 1 < scalar_bits.len() {
1066                p = self.double(layouter, &p)?;
1067            }
1068        }
1069
1070        Ok(res.unwrap_or(self.assign_fixed(layouter, C::CryptographicGroup::identity())?))
1071    }
1072
1073    fn point_from_coordinates(
1074        &self,
1075        layouter: &mut impl Layouter<F>,
1076        x: &AssignedField<F, C::Base, B>,
1077        y: &AssignedField<F, C::Base, B>,
1078    ) -> Result<Self::Point, Error> {
1079        let p_value = x.value().zip(y.value()).map_with_result(|(x, y)| {
1080            C::from_xy(x, y)
1081                .map(|p| p.into_subgroup())
1082                .ok_or(Error::Synthesis("invalid coordinates".into()))
1083        })?;
1084
1085        let p = self.assign(layouter, p_value)?;
1086
1087        self.base_field_chip.assert_equal(layouter, x, &self.x_coordinate(&p))?;
1088        self.base_field_chip.assert_equal(layouter, y, &self.y_coordinate(&p))?;
1089
1090        Ok(p)
1091    }
1092
1093    fn x_coordinate(&self, point: &Self::Point) -> Self::Coordinate {
1094        point.x.clone()
1095    }
1096
1097    fn y_coordinate(&self, point: &Self::Point) -> Self::Coordinate {
1098        point.y.clone()
1099    }
1100
1101    fn base_field(&self) -> &impl DecompositionInstructions<F, Self::Coordinate> {
1102        self.base_field_chip()
1103    }
1104
1105    fn assign_without_subgroup_check(
1106        &self,
1107        layouter: &mut impl Layouter<F>,
1108        value: Value<C::CryptographicGroup>,
1109    ) -> Result<Self::Point, Error> {
1110        let p = self.assign_point_unchecked(layouter, value)?;
1111        self.assert_on_curve(layouter, &p.x, &p.y)?;
1112        Ok(p)
1113    }
1114}
1115
1116/// Precomputed table of points for windowed MSM.
1117#[derive(Clone, Debug)]
1118struct PrecomputedTable<F, C, B, const WS: usize>
1119where
1120    F: CircuitField,
1121    C: EdwardsCurve,
1122    B: FieldEmulationParams<F, C::Base>,
1123{
1124    /// Table of precomputed points, where `table[i] = i * base`.
1125    table: Vec<AssignedForeignEdwardsPoint<F, C, B>>,
1126}
1127
1128impl<F, C, B, S, N> ForeignEdwardsEccChip<F, C, B, S, N>
1129where
1130    F: CircuitField,
1131    C: EdwardsCurve,
1132    B: FieldEmulationParams<F, C::Base>,
1133    S: ScalarFieldInstructions<F>,
1134    S::Scalar: InnerValue<Element = C::ScalarField>,
1135    N: NativeInstructions<F>,
1136{
1137    /// Builds table `[0*base, 1*base, ..., (2^WS-1)*base]`.
1138    /// Uses doubling for even indices (`table[2k] = double(table[k])`) and
1139    /// addition for odd indices (`table[2k+1] = add(table[2k], base)`),
1140    /// which is cheaper than a linear chain of additions.
1141    fn precompute<const WS: usize>(
1142        &self,
1143        layouter: &mut impl Layouter<F>,
1144        base: &AssignedForeignEdwardsPoint<F, C, B>,
1145    ) -> Result<PrecomputedTable<F, C, B, WS>, Error> {
1146        let identity = self.assign_fixed(layouter, C::CryptographicGroup::identity())?;
1147        let mut table = vec![identity, base.clone()];
1148        for i in 2..1 << WS {
1149            let entry = if i % 2 == 0 {
1150                self.double(layouter, &table[i / 2])?
1151            } else {
1152                self.add(layouter, &table[i - 1], base)?
1153            };
1154            table.push(entry);
1155        }
1156        Ok(PrecomputedTable { table })
1157    }
1158
1159    /// Delegates to [`fill_dynamic_lookup_row`] with this chip's columns.
1160    #[allow(clippy::type_complexity)]
1161    fn fill_dynamic_lookup_row(
1162        &self,
1163        layouter: &mut impl Layouter<F>,
1164        point: &AssignedForeignEdwardsPoint<F, C, B>,
1165        index: &AssignedNative<F>,
1166        table_tag: F,
1167        enable_lookup: bool,
1168    ) -> Result<(Vec<AssignedNative<F>>, Vec<AssignedNative<F>>), Error> {
1169        fill_dynamic_lookup_row(
1170            layouter,
1171            &point.x.limb_values(),
1172            &point.y.limb_values(),
1173            index,
1174            &self.config.base_field_config.x_cols,
1175            &self.config.base_field_config.z_cols, // z_cols used for y (y_cols == x_cols)
1176            self.config.idx_col_multi_select,
1177            self.config.tag_col_multi_select,
1178            self.config.q_multi_select,
1179            table_tag,
1180            enable_lookup,
1181        )
1182    }
1183
1184    /// Loads a precomputed point table into the dynamic lookup.  Entry `i` is
1185    /// paired with index `i` and the given `table_tag`.
1186    fn load_multi_select_table(
1187        &self,
1188        layouter: &mut impl Layouter<F>,
1189        point_table: &[AssignedForeignEdwardsPoint<F, C, B>],
1190        table_tag: F,
1191    ) -> Result<(), Error> {
1192        for (i, point) in point_table.iter().enumerate() {
1193            let index = self.native_gadget.assign_fixed(layouter, F::from(i as u64))?;
1194            self.fill_dynamic_lookup_row(layouter, point, &index, table_tag, false)?;
1195        }
1196        Ok(())
1197    }
1198
1199    /// Returns `point_table[selector]` using the dynamic lookup.
1200    ///
1201    /// The table must have been loaded via [`Self::load_multi_select_table`]
1202    /// with the same `table_tag`.
1203    fn multi_select(
1204        &self,
1205        layouter: &mut impl Layouter<F>,
1206        selector: &AssignedNative<F>,
1207        point_table: &[AssignedForeignEdwardsPoint<F, C, B>],
1208        table_tag: F,
1209    ) -> Result<AssignedForeignEdwardsPoint<F, C, B>, Error> {
1210        let mut selector_idx = 0usize;
1211        selector.value().map(|v| {
1212            let digits = v.to_biguint().to_u32_digits();
1213            let digit = if digits.is_empty() { 0 } else { digits[0] };
1214            debug_assert!(digits.len() <= 1);
1215            debug_assert!((digit as usize) < point_table.len());
1216            selector_idx = digit as usize;
1217        });
1218
1219        let selected = point_table[selector_idx].clone();
1220
1221        let (xs, ys) =
1222            self.fill_dynamic_lookup_row(layouter, &selected, selector, table_tag, true)?;
1223        let x = AssignedField::<F, C::Base, B>::from_limbs_unsafe(xs);
1224        let y = AssignedField::<F, C::Base, B>::from_limbs_unsafe(ys);
1225
1226        Ok(AssignedForeignEdwardsPoint::<F, C, B> {
1227            point: selected.point,
1228            x,
1229            y,
1230        })
1231    }
1232
1233    /// Windowed interleaved MSM over pre-chunked scalars. Each scalar is a
1234    /// sequence of WS-bit window values (native field elements). Shares the
1235    /// doubling chain across all bases. Horner evaluation. Scalars may have
1236    /// different numbers of windows; short scalars are skipped in high windows.
1237    /// Point selection uses a dynamic-lookup table.
1238    fn windowed_msm<const WS: usize>(
1239        &self,
1240        layouter: &mut impl Layouter<F>,
1241        scalars: &[Vec<AssignedNative<F>>],
1242        bases: &[AssignedForeignEdwardsPoint<F, C, B>],
1243    ) -> Result<AssignedForeignEdwardsPoint<F, C, B>, Error> {
1244        assert_eq!(scalars.len(), bases.len());
1245
1246        if bases.is_empty() {
1247            return self.assign_fixed(layouter, C::CryptographicGroup::identity());
1248        }
1249
1250        // Number of windows per scalar.
1251        let num_windows: Vec<usize> = scalars.iter().map(|s| s.len()).collect();
1252        let max_num_windows = *num_windows.iter().max().unwrap();
1253
1254        // Precompute tables for each base and load them into the dynamic lookup.
1255        let tag_cnt = *self.tag_cnt.borrow();
1256        self.tag_cnt.replace(tag_cnt + bases.len() as u64);
1257        debug_assert!(F::NUM_BITS > 64);
1258
1259        let mut tables = vec![];
1260        for (i, base) in bases.iter().enumerate() {
1261            let table = self.precompute::<WS>(layouter, base)?;
1262            self.load_multi_select_table(layouter, &table.table, F::from(tag_cnt + i as u64))?;
1263            tables.push(table);
1264        }
1265
1266        let mut res = self.assign_fixed(layouter, C::CryptographicGroup::identity())?;
1267        for w in (0..max_num_windows).rev() {
1268            // Skip doubling in the most-significant window.
1269            if w < max_num_windows - 1 {
1270                for _ in 0..WS {
1271                    res = self.double(layouter, &res)?;
1272                }
1273            }
1274            for (i, (windows, nw)) in scalars.iter().zip(&num_windows).enumerate() {
1275                // Skip scalars that have no windows in this position.
1276                if w >= *nw {
1277                    continue;
1278                }
1279                let addend = self.multi_select(
1280                    layouter,
1281                    &windows[w],
1282                    &tables[i].table,
1283                    F::from(tag_cnt + i as u64),
1284                )?;
1285                res = self.add(layouter, &res, &addend)?;
1286            }
1287        }
1288
1289        Ok(res)
1290    }
1291}
1292
1293#[derive(Clone, Debug)]
1294#[cfg(any(test, feature = "testing"))]
1295/// Configuration used to implement `FromScratch` for the
1296/// `ForeignEdwardsEccChip` chip. This should only be used for testing.
1297pub struct ForeignEdwardsEccTestConfig<F, C, S, N>
1298where
1299    F: CircuitField,
1300    C: EdwardsCurve,
1301    S: ScalarFieldInstructions<F> + FromScratch<F>,
1302    S::Scalar: InnerValue<Element = C::ScalarField>,
1303    N: NativeInstructions<F> + FromScratch<F>,
1304{
1305    native_gadget_config: <N as FromScratch<F>>::Config,
1306    scalar_field_config: <S as FromScratch<F>>::Config,
1307    ff_ecc_config: ForeignEdwardsEccConfig<C>,
1308}
1309
1310#[cfg(any(test, feature = "testing"))]
1311impl<F, C, B, S, N> FromScratch<F> for ForeignEdwardsEccChip<F, C, B, S, N>
1312where
1313    F: CircuitField,
1314    C: EdwardsCurve,
1315    C::Base: Legendre,
1316    B: FieldEmulationParams<F, C::Base>,
1317    S: ScalarFieldInstructions<F> + FromScratch<F>,
1318    S::Scalar: InnerValue<Element = C::ScalarField>,
1319    N: NativeInstructions<F> + FromScratch<F>,
1320{
1321    type Config = ForeignEdwardsEccTestConfig<F, C, S, N>;
1322
1323    fn new_from_scratch(config: &Self::Config) -> Self {
1324        let native_gadget = <N as FromScratch<F>>::new_from_scratch(&config.native_gadget_config);
1325        let scalar_field_chip =
1326            <S as FromScratch<F>>::new_from_scratch(&config.scalar_field_config);
1327        ForeignEdwardsEccChip::new(&config.ff_ecc_config, &native_gadget, &scalar_field_chip)
1328    }
1329
1330    fn load_from_scratch(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
1331        self.native_gadget.load_from_scratch(layouter)?;
1332        self.scalar_field_chip.load_from_scratch(layouter)
1333    }
1334
1335    fn configure_from_scratch(
1336        meta: &mut ConstraintSystem<F>,
1337        advice_columns: &mut Vec<Column<Advice>>,
1338        fixed_columns: &mut Vec<Column<Fixed>>,
1339        instance_columns: &[Column<Instance>; 2],
1340    ) -> ForeignEdwardsEccTestConfig<F, C, S, N> {
1341        use crate::field::foreign::nb_field_chip_columns;
1342
1343        let native_gadget_config = <N as FromScratch<F>>::configure_from_scratch(
1344            meta,
1345            advice_columns,
1346            fixed_columns,
1347            instance_columns,
1348        );
1349        let scalar_field_config = <S as FromScratch<F>>::configure_from_scratch(
1350            meta,
1351            advice_columns,
1352            fixed_columns,
1353            instance_columns,
1354        );
1355        let nb_advice_cols = nb_field_chip_columns::<F, C::Base, B>();
1356        while advice_columns.len() < nb_advice_cols {
1357            advice_columns.push(meta.advice_column());
1358        }
1359        let nb_parallel_range_checks = 4;
1360        let max_bit_len = 8;
1361        let base_field_config = FieldChip::<F, C::Base, B, N>::configure(
1362            meta,
1363            &advice_columns[..nb_advice_cols],
1364            nb_parallel_range_checks,
1365            max_bit_len,
1366        );
1367        let ff_ecc_config = ForeignEdwardsEccChip::<F, C, B, S, N>::configure(
1368            meta,
1369            &base_field_config,
1370            advice_columns,
1371            fixed_columns,
1372            nb_parallel_range_checks,
1373            max_bit_len,
1374        );
1375        ForeignEdwardsEccTestConfig {
1376            native_gadget_config,
1377            scalar_field_config,
1378            ff_ecc_config,
1379        }
1380    }
1381}
1382
1383#[cfg(test)]
1384mod tests {
1385    use ff::Field;
1386    use group::{Group, GroupEncoding};
1387    use midnight_curves::{curve25519::Curve25519, BlsScalar};
1388    use midnight_proofs::{circuit::SimpleFloorPlanner, dev::MockProver, plonk::Circuit};
1389    use rand::SeedableRng;
1390    use rand_chacha::ChaCha8Rng;
1391
1392    use super::*;
1393    use crate::{
1394        ecc::curves::CircuitCurve,
1395        field::{
1396            decomposition::chip::P2RDecompositionChip, foreign::params::MultiEmulationParams,
1397            NativeChip, NativeGadget,
1398        },
1399        instructions::{assertions, control_flow, ecc, equality, public_input, zero},
1400    };
1401
1402    type F = BlsScalar;
1403    type Native<F> = NativeGadget<F, P2RDecompositionChip<F>, NativeChip<F>>;
1404    type EmulatedField<F, C> = FieldChip<F, <C as Group>::Scalar, MultiEmulationParams, Native<F>>;
1405
1406    macro_rules! test_generic {
1407        ($mod:ident, $op:ident, $native:ty, $curve:ty, $scalar_field:ty,
1408    $name:expr) => {
1409            $mod::tests::$op::<
1410                $native,
1411                AssignedForeignEdwardsPoint<$native, $curve, MultiEmulationParams>,
1412                ForeignEdwardsEccChip<
1413                    $native,
1414                    $curve,
1415                    MultiEmulationParams,
1416                    $scalar_field,
1417                    Native<$native>,
1418                >,
1419            >($name);
1420        };
1421    }
1422
1423    macro_rules! test {
1424        ($mod:ident, $op:ident) => {
1425            #[test]
1426            fn $op() {
1427                test_generic!($mod, $op, F, Curve25519, EmulatedField<F, Curve25519>, "emulated_curve25519");
1428            }
1429        };
1430    }
1431
1432    test!(assertions, test_assertions);
1433
1434    test!(public_input, test_public_inputs);
1435
1436    test!(equality, test_is_equal);
1437
1438    test!(zero, test_zero_assertions);
1439    test!(zero, test_is_zero);
1440
1441    test!(control_flow, test_select);
1442    test!(control_flow, test_cond_assert_equal);
1443    test!(control_flow, test_cond_swap);
1444
1445    macro_rules! ecc_test {
1446        ($op:ident, $native:ty, $curve:ty, $scalar_field:ty, $name:expr) => {
1447            ecc::tests::$op::<
1448                $native,
1449                $curve,
1450                ForeignEdwardsEccChip<
1451                    $native,
1452                    $curve,
1453                    MultiEmulationParams,
1454                    $scalar_field,
1455                    Native<$native>,
1456                >,
1457            >($name);
1458        };
1459    }
1460
1461    macro_rules! ecc_tests {
1462        ($op:ident) => {
1463            #[test]
1464            fn $op() {
1465                ecc_test!($op, BlsScalar, Curve25519, EmulatedField<BlsScalar, Curve25519>, "emulated_curve25519");
1466            }
1467        };
1468    }
1469
1470    ecc_tests!(test_assign);
1471    ecc_tests!(test_assign_without_subgroup_check);
1472    ecc_tests!(test_add);
1473    ecc_tests!(test_double);
1474    ecc_tests!(test_negate);
1475    ecc_tests!(test_msm);
1476    ecc_tests!(test_msm_by_bounded_scalars);
1477    ecc_tests!(test_mul_by_constant);
1478    ecc_tests!(test_coordinates);
1479
1480    #[test]
1481    fn test_assert_on_curve() {
1482        run_test_assert_on_curve::<Curve25519>();
1483    }
1484
1485    /// Negative tests for `assert_on_curve`. Positive cases (identity,
1486    /// generator, random points) are covered by the generic `test_assign`.
1487    fn run_test_assert_on_curve<C>()
1488    where
1489        C: EdwardsCurve,
1490        C::Base: Legendre,
1491        MultiEmulationParams: FieldEmulationParams<BlsScalar, C::Base>
1492            + FieldEmulationParams<BlsScalar, C::ScalarField>,
1493    {
1494        fn assert_not_on_curve<C: EdwardsCurve>(x: C::Base, y: C::Base)
1495        where
1496            C::Base: Legendre,
1497            MultiEmulationParams: FieldEmulationParams<BlsScalar, C::Base>
1498                + FieldEmulationParams<BlsScalar, C::ScalarField>,
1499        {
1500            let circuit = OnCurveCheckCircuit::<C> { x, y };
1501            let prover = MockProver::run(&circuit, vec![vec![], vec![]])
1502                .expect("proof generation should not fail");
1503            assert!(prover.verify().is_err());
1504        }
1505
1506        let mut rng = ChaCha8Rng::seed_from_u64(0x0);
1507
1508        // Random point with y offset by 1
1509        let point = C::CryptographicGroup::random(&mut rng);
1510        let (x, y) = point.into().coordinates().expect("valid curve point");
1511
1512        assert_not_on_curve::<C>(x, y + C::Base::ONE);
1513        assert_not_on_curve::<C>(C::Base::ONE, C::Base::ONE);
1514        assert_not_on_curve::<C>(C::Base::ZERO, C::Base::ZERO);
1515    }
1516
1517    type EdwardsChip<C> = ForeignEdwardsEccChip<
1518        F,
1519        C,
1520        MultiEmulationParams,
1521        FieldChip<F, <C as CircuitCurve>::ScalarField, MultiEmulationParams, Native<F>>,
1522        Native<F>,
1523    >;
1524
1525    /// Test circuit that calls `assert_on_curve` for arbitrary (x, y)
1526    /// coordinates (not necessarily representing a valid curve point).
1527    ///
1528    /// This circuit checks if `assert_on_curve` correctly verifies, or fails,
1529    /// on a selected set of inputs.
1530    #[derive(Clone, Debug)]
1531    struct OnCurveCheckCircuit<C: EdwardsCurve> {
1532        x: C::Base,
1533        y: C::Base,
1534    }
1535
1536    impl<C> Circuit<F> for OnCurveCheckCircuit<C>
1537    where
1538        C: EdwardsCurve,
1539        C::Base: Legendre,
1540        MultiEmulationParams:
1541            FieldEmulationParams<F, C::Base> + FieldEmulationParams<F, C::ScalarField>,
1542    {
1543        type Config = <EdwardsChip<C> as FromScratch<F>>::Config;
1544        type FloorPlanner = SimpleFloorPlanner;
1545        type Params = ();
1546
1547        fn without_witnesses(&self) -> Self {
1548            unreachable!()
1549        }
1550
1551        fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
1552            let committed = meta.instance_column();
1553            let instance = meta.instance_column();
1554            EdwardsChip::<C>::configure_from_scratch(
1555                meta,
1556                &mut vec![],
1557                &mut vec![],
1558                &[committed, instance],
1559            )
1560        }
1561
1562        fn synthesize(
1563            &self,
1564            config: Self::Config,
1565            mut layouter: impl Layouter<F>,
1566        ) -> Result<(), Error> {
1567            let chip = EdwardsChip::<C>::new_from_scratch(&config);
1568
1569            let x = chip.base_field_chip().assign(&mut layouter, Value::known(self.x))?;
1570            let y = chip.base_field_chip().assign(&mut layouter, Value::known(self.y))?;
1571
1572            chip.assert_on_curve(&mut layouter, &x, &y)?;
1573            chip.load_from_scratch(&mut layouter)
1574        }
1575    }
1576
1577    /// Test circuit that calls `from_canonical_compressed_bytes` on a
1578    /// given byte array together with the claimed subgroup point.
1579    ///
1580    /// The proof succeeds if and only if the byte array is the canonical
1581    /// encoding of the subgroup point.
1582    #[derive(Clone, Debug)]
1583    struct FromCompressedBytesCheckCircuit {
1584        point: Curve25519Subgroup,
1585        bytes: [u8; 32],
1586    }
1587
1588    impl Circuit<F> for FromCompressedBytesCheckCircuit {
1589        type Config = <EdwardsChip<Curve25519> as FromScratch<F>>::Config;
1590        type FloorPlanner = SimpleFloorPlanner;
1591        type Params = ();
1592
1593        fn without_witnesses(&self) -> Self {
1594            unreachable!()
1595        }
1596
1597        fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
1598            let committed = meta.instance_column();
1599            let instance = meta.instance_column();
1600            EdwardsChip::<Curve25519>::configure_from_scratch(
1601                meta,
1602                &mut vec![],
1603                &mut vec![],
1604                &[committed, instance],
1605            )
1606        }
1607
1608        fn synthesize(
1609            &self,
1610            config: Self::Config,
1611            mut layouter: impl Layouter<F>,
1612        ) -> Result<(), Error> {
1613            let chip = EdwardsChip::<Curve25519>::new_from_scratch(&config);
1614
1615            let byte_cells: [AssignedByte<F>; 32] = self
1616                .bytes
1617                .iter()
1618                .map(|b| chip.native_gadget.assign(&mut layouter, Value::known(*b)))
1619                .collect::<Result<Vec<_>, _>>()?
1620                .try_into()
1621                .expect("exactly 32 bytes");
1622
1623            let _ = chip.from_canonical_compressed_bytes(
1624                &mut layouter,
1625                &byte_cells,
1626                Value::known(self.point),
1627            )?;
1628
1629            chip.load_from_scratch(&mut layouter)
1630        }
1631    }
1632
1633    fn run_test_compressed_bytes(point: Curve25519Subgroup, bytes: [u8; 32], should_accept: bool) {
1634        let circuit = FromCompressedBytesCheckCircuit { point, bytes };
1635        let prover = MockProver::run(&circuit, vec![vec![], vec![]])
1636            .expect("proof generation should not fail");
1637        assert_eq!(prover.verify().is_ok(), should_accept);
1638    }
1639
1640    #[test]
1641    fn test_compressed_bytes() {
1642        // Canonical LE encoding of the identity with y = 1 and sign_x = 0.
1643        let mut canonical = [0; 32];
1644        canonical[0] = 1;
1645        run_test_compressed_bytes(Curve25519Subgroup::identity(), canonical, true);
1646
1647        // Non-canonical LE encoding of the identity with y = 2^255 - 18 and sign_x = 0.
1648        let mut non_canonical = [0xff_u8; 32];
1649        non_canonical[0] = 0xee;
1650        non_canonical[31] = 0x7f;
1651        run_test_compressed_bytes(Curve25519Subgroup::identity(), non_canonical, false);
1652
1653        // Non-canonical LE encoding of the identity with y = 1 and sign_x = 1.
1654        let mut non_canonical_with_sign = canonical;
1655        non_canonical_with_sign[31] = 0x80;
1656        run_test_compressed_bytes(
1657            Curve25519Subgroup::identity(),
1658            non_canonical_with_sign,
1659            false,
1660        );
1661
1662        // Canonical LE encoding of the subgroup generator.
1663        let g = Curve25519Subgroup::generator();
1664        run_test_compressed_bytes(g, Curve25519::from(g).to_bytes(), true);
1665
1666        // Canonical LE encoding of a random subgroup point.
1667        let mut rng = ChaCha8Rng::seed_from_u64(0x7374727564656C);
1668        let p = Curve25519Subgroup::random(&mut rng);
1669        run_test_compressed_bytes(p, Curve25519::from(p).to_bytes(), true);
1670    }
1671}