Skip to main content

midnight_circuits/ecc/native/
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//! Generic Chip implementation for the ECC Instructions over twisted Edwards
15//! curves. Indeed, this chip only implements partially generic twisted Edwards
16//! curve, i.e. with a = -1, which is the case of Jubjub.
17
18use ecc::EccInstructions;
19use ff::{Field, PrimeField};
20use group::Group;
21use midnight_proofs::{
22    circuit::{Chip, Layouter, Region, Value},
23    plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Expression, Selector},
24    poly::Rotation,
25};
26#[cfg(any(test, feature = "testing"))]
27use {
28    crate::field::decomposition::chip::P2RDecompositionConfig,
29    crate::testing_utils::{FromScratch, Sampleable},
30    midnight_proofs::plonk::Instance,
31    rand::RngCore,
32};
33
34use crate::{
35    ecc::curves::{CircuitCurve, EdwardsCurve},
36    field::{decomposition::chip::P2RDecompositionChip, NativeChip, NativeGadget},
37    instructions::*,
38    types::{AssignedBit, AssignedByte, AssignedNative, InnerConstants, InnerValue, Instantiable},
39    utils::{
40        util::{fe_to_le_bits, le_bits_to_field_elem},
41        ComposableChip,
42    },
43};
44
45/// The number of advice columns used by the EccChip.
46pub const NB_EDWARDS_COLS: usize = 9;
47
48/// A twisted Edwards curve point represented in affine (x, y) coordinates, the
49/// identity represented as (0, 1).
50#[derive(Clone, Debug)]
51pub struct AssignedNativePoint<C: CircuitCurve> {
52    x: AssignedNative<C::Base>,
53    y: AssignedNative<C::Base>,
54}
55
56impl<C: CircuitCurve> InnerValue for AssignedNativePoint<C> {
57    type Element = C::CryptographicGroup;
58
59    fn value(&self) -> Value<Self::Element> {
60        self.x
61            .value()
62            .zip(self.y.value())
63            .map(|(x, y)| C::from_xy(*x, *y).expect("non-id").into_subgroup())
64    }
65}
66
67impl<C: CircuitCurve> AssignedNativePoint<C> {
68    // To ensure type safety, we expect all assigned values to belong to the
69    // subgroup. However, for Multi-Table Commitment (MTC), we may be working with
70    // points on the full curve rather than strictly within the subgroup.
71    //
72    // As a result, we cannot generically treat the inner type of the curve using
73    // the `InnerValue` trait, as this trait assumes subgroup membership by directly
74    // unwrapping the `into_subgroup` function.
75    //
76    // Certain internal functions, such as `assign_double_add` and
77    // `assign_cond_add`, operate on points that may lie on the full curve (not
78    // only the subgroup). To handle these cases safely, we use this auxiliary
79    // closure to avoid unintentional assumptions about subgroup membership.
80    /// Return the value of the assigned point
81    fn curve_value(&self) -> Value<C> {
82        self.x
83            .value()
84            .zip(self.y.value())
85            .map(|(x, y)| C::from_xy(*x, *y).expect("Valid coordinates."))
86    }
87}
88
89impl<C: CircuitCurve> Instantiable<C::Base> for AssignedNativePoint<C> {
90    fn as_public_input(p: &C::CryptographicGroup) -> Vec<C::Base> {
91        let point: C = (*p).into();
92        let coordinates = point.coordinates().expect("non-id");
93        vec![coordinates.0, coordinates.1]
94    }
95
96    fn from_public_input(fields: &[C::Base]) -> Option<C::CryptographicGroup> {
97        if fields.len() != 2 {
98            return None;
99        }
100        C::from_xy(fields[0], fields[1]).map(|p| p.into_subgroup())
101    }
102}
103
104impl<C: EdwardsCurve> InnerConstants for AssignedNativePoint<C> {
105    fn inner_zero() -> C::CryptographicGroup {
106        C::CryptographicGroup::identity()
107    }
108
109    fn inner_one() -> Self::Element {
110        C::CryptographicGroup::generator()
111    }
112}
113
114/// Scalars are represented as a vector of assigned bits in little endian.
115#[derive(Clone, Debug)]
116pub struct AssignedScalarOfNativeCurve<C: CircuitCurve>(Vec<AssignedBit<C::Base>>);
117
118impl<C: CircuitCurve> InnerValue for AssignedScalarOfNativeCurve<C> {
119    type Element = C::Scalar;
120
121    fn value(&self) -> Value<Self::Element> {
122        let bools = self.0.iter().map(|b| b.value());
123        let value_bools: Value<Vec<bool>> = Value::from_iter(bools);
124        value_bools.map(|le_bits| le_bits_to_field_elem::<C::Scalar>(&le_bits))
125    }
126}
127
128impl<C: EdwardsCurve> Instantiable<C::Base> for AssignedScalarOfNativeCurve<C> {
129    fn as_public_input(element: &C::Scalar) -> Vec<C::Base> {
130        // We aggregate the bits while they fit in a single `C::Base` value.
131        let nb_bits_per_batch = C::Base::NUM_BITS as usize - 1;
132        fe_to_le_bits(element, Some(C::NUM_BITS_SUBGROUP as usize))
133            .chunks(nb_bits_per_batch)
134            .map(le_bits_to_field_elem)
135            .collect()
136    }
137
138    fn from_public_input(fields: &[C::Base]) -> Option<C::Scalar> {
139        let nb_bits_per_batch = C::Base::NUM_BITS as usize - 1;
140
141        let bits: Vec<bool> =
142            fields.iter().flat_map(|f| fe_to_le_bits(f, Some(nb_bits_per_batch))).collect();
143
144        let (head, tail) = bits.split_at(C::NUM_BITS_SUBGROUP as usize);
145        if tail.iter().any(|b| *b) {
146            return None;
147        }
148
149        Some(le_bits_to_field_elem(head))
150    }
151}
152
153impl<C: EdwardsCurve> InnerConstants for AssignedScalarOfNativeCurve<C> {
154    fn inner_zero() -> C::Scalar {
155        C::Scalar::ZERO
156    }
157    fn inner_one() -> C::Scalar {
158        C::Scalar::ONE
159    }
160}
161
162#[cfg(any(test, feature = "testing"))]
163impl<C: EdwardsCurve> Sampleable for AssignedScalarOfNativeCurve<C> {
164    fn sample_inner(rng: impl RngCore) -> C::Scalar {
165        C::Scalar::random(rng)
166    }
167}
168
169/// [`EccConfig`], which uses [`NB_EDWARDS_COLS`] advice columns.
170#[derive(Clone, Debug)]
171pub struct EccConfig {
172    pub(crate) q_double: Selector,
173    pub(crate) q_cond_add: Selector,
174    pub(crate) q_mem: Selector,
175    pub(crate) advice_cols: [Column<Advice>; NB_EDWARDS_COLS],
176}
177
178impl EccConfig {
179    /// Enforce `Q = 2 * P`, using columns:
180    ///
181    /// ```text
182    ///    0      1      2      3       4      5      6       7      8     
183    /// ------------------------------------------------------------------
184    /// |      |      |      |      |      |  xp  |  yp  | xp_xp |       |
185    /// |  xq  |  yq  |      |      |      |      |      |       |       |
186    /// ------------------------------------------------------------------
187    /// ```
188    ///
189    /// The curve equation is `-x^2 + y^2 = 1 + d * x^2 * y^2`.
190    /// The result of doubling, the point `Q = (xq, yq)`, can be computed as:
191    /// * `xq = (2 * xp * yp) / (1 + d * xp * xp * yp * yp)`
192    /// * `yq = (yp * yp + xp * xp) / (1 - d * xp * xp * yp * yp)`
193    ///
194    /// Equivalently, the above can be computed as:
195    /// * `xq * (1 + d * xp * xp * yp * yp) = 2 * xp * yp`
196    /// * `yq * (1 - d * xp * xp * yp * yp) = yp * yp + xp * xp`
197    ///
198    /// Note, that `d * xp * xp * yp * yp != 1,-1` if `P` satisfies the
199    /// curve equation (since `-1` is a square and `d` is not a square
200    /// in the base field).
201    /// See <https://eprint.iacr.org/2008/013.pdf>.
202    ///
203    /// Enforce the constraints:
204    /// * `xq * (1 + d * xp_xp * yp * yp) = 2 * xp * yp`
205    /// * `yq * (1 - d * xp_xp * yp * yp) = yp * yp + xp * xp`
206    /// * `xp_xp = xp * xp`
207    fn create_double_gate<C: EdwardsCurve>(
208        &self,
209        meta: &mut ConstraintSystem<C::Base>,
210        q_double: &Selector,
211    ) {
212        meta.create_gate("double", |meta| {
213            let xp = meta.query_advice(self.advice_cols[5], Rotation::cur());
214            let yp = meta.query_advice(self.advice_cols[6], Rotation::cur());
215            let xq = meta.query_advice(self.advice_cols[0], Rotation::next());
216            let yq = meta.query_advice(self.advice_cols[1], Rotation::next());
217
218            let xp_xp = meta.query_advice(self.advice_cols[7], Rotation::cur());
219
220            let one = Expression::from(1);
221            let edwards_d = Expression::Constant(C::D);
222            let xp_yp = &xp * &yp;
223            let yp_yp = yp.square();
224            let d_xp_xp_yp_yp = edwards_d * &xp_xp * &yp_yp;
225
226            let id1 = xq * (&one + &d_xp_xp_yp_yp) - (xp_yp.clone() + xp_yp);
227            let id2 = yq * (one - d_xp_xp_yp_yp) - (yp_yp + &xp_xp);
228            let id3 = xp.clone() * xp - xp_xp;
229
230            Constraints::with_selector(
231                *q_double,
232                vec![
233                    ("qx constraint for q = 2 * p", id1),
234                    ("qy constraint for q = 2 * p", id2),
235                    ("constraint for xp_xp = xp * xp", id3),
236                ],
237            )
238        })
239    }
240
241    /// Enforce `R = Q + b * S`, using columns:
242    ///
243    /// ```text
244    ///    0      1      2      3       4      5      6      7         8
245    /// -----------------------------------------------------------------------
246    /// |  xq  |  yq  |  xs  |  ys  |   b   |  xr  |  yr  |     | xq_yq_xs_ys |
247    /// -----------------------------------------------------------------------
248    /// ```
249    ///
250    /// The curve equation is `-x^2 + y^2 = 1 + d * x^2 * y^2`.
251    /// The result, `R = (xr, yr)`, can be computed as:
252    /// * `xr = (xq + b * (xq*ys + xs*yq - xq)) / (1 + b*d * xq*xs*yq*ys)`
253    /// * `yr = (yq + b * (yq*ys + xq*xs - yq)) / (1 - b*d * xq*xs*yq*ys)`
254    ///
255    /// Equivalently, the above can be computed as:
256    /// * `xr * (1 + b * d * xq * xs * yq * ys) = xq + b * (xq*ys + xs*yq - xq)`
257    /// * `yr * (1 - b * d * xq * xs * yq * ys) = yq + b * (yq*ys + xq*xs - yq)`
258    ///
259    /// Note, that `b * d * xq * xs * yq * ys != 1,-1` if `Q`, `S` satisfy the
260    /// curve equation (since `-1` is a square and `d` is not a square
261    /// in the base field).
262    /// See <https://eprint.iacr.org/2008/013.pdf>.
263    ///
264    /// Enforce the constraints:
265    /// * `xr * (1 + b * d * xq_yq_xs_ys) = xq + b * (xq*ys + xs*yq - xq)`
266    /// * `yr * (1 - b * d * xq_yq_xs_ys) = yq + b * (yq*ys + xq*xs - yq)`
267    /// * `xq_yq_xs_ys = xq * yq * xs * ys`
268    fn create_cond_add_gate<C: EdwardsCurve>(
269        &self,
270        meta: &mut ConstraintSystem<C::Base>,
271        q_cond_add: &Selector,
272    ) {
273        meta.create_gate("conditional add", |meta| {
274            let xq = meta.query_advice(self.advice_cols[0], Rotation::cur());
275            let yq = meta.query_advice(self.advice_cols[1], Rotation::cur());
276            let xs = meta.query_advice(self.advice_cols[2], Rotation::cur());
277            let ys = meta.query_advice(self.advice_cols[3], Rotation::cur());
278            let xr = meta.query_advice(self.advice_cols[5], Rotation::cur());
279            let yr = meta.query_advice(self.advice_cols[6], Rotation::cur());
280            let b = meta.query_advice(self.advice_cols[4], Rotation::cur());
281
282            let one = Expression::from(1);
283            let edwards_d = Expression::Constant(C::D);
284
285            let xq_yq_xs_ys = meta.query_advice(self.advice_cols[8], Rotation::cur());
286
287            let xq_xs = &xq * &xs;
288            let yq_ys = &yq * &ys;
289            let xq_ys = &xq * &ys;
290            let xs_yq = &xs * &yq;
291            let b_d_xq_xs_yq_ys = &b * edwards_d * &xq_yq_xs_ys;
292
293            let id1 = xr * (&one + &b_d_xq_xs_yq_ys) - (&xq + &b * (xq_ys + xs_yq - &xq));
294            let id2 = yr * (one - b_d_xq_xs_yq_ys) - (&yq + b * (yq_ys + xq_xs - &yq));
295            let id3 = xq_yq_xs_ys - xq * yq * xs * ys;
296
297            Constraints::with_selector(
298                *q_cond_add,
299                vec![
300                    ("rx constraint for r = q + b * s", id1),
301                    ("ry constraint for r = q + b * s", id2),
302                    ("constraint for xq_yq_xs_ys = xq * yq * xs * ys", id3),
303                ],
304            )
305        })
306    }
307
308    /// Enforce `P = (x, y)` is on the curve, using columns:
309    ///
310    /// ```text
311    /// -------------
312    /// |  x  |  y  |
313    /// -------------
314    /// ```
315    ///
316    /// Enforce the constraint:
317    /// * `-x^2 + y^2 = 1 + d * x^2 * y^2`
318    fn create_membership_gate<C: EdwardsCurve>(
319        &self,
320        meta: &mut ConstraintSystem<C::Base>,
321        q_point: &Selector,
322    ) {
323        meta.create_gate("witness point", |meta| {
324            let x = meta.query_advice(self.advice_cols[0], Rotation::cur());
325            let y = meta.query_advice(self.advice_cols[1], Rotation::cur());
326
327            let one = Expression::from(1);
328            let edwards_d = Expression::Constant(C::D);
329
330            let x2 = x.square();
331            let y2 = y.square();
332
333            let id = y2.clone() - x2.clone() - (one + edwards_d * x2 * y2);
334
335            Constraints::with_selector(*q_point, vec![("curve equation", id)])
336        })
337    }
338}
339
340type NG<F> = NativeGadget<F, P2RDecompositionChip<F>, NativeChip<F>>;
341
342/// A native  [`EccInstructions`] chip.
343/// Since the chip is native, it only supports the embedded curve Jubjub.
344#[derive(Clone, Debug)]
345pub struct EccChip<C: EdwardsCurve> {
346    config: EccConfig,
347    native_gadget: NG<C::Base>,
348}
349
350impl<C: EdwardsCurve> Chip<C::Base> for EccChip<C> {
351    type Config = EccConfig;
352    type Loaded = ();
353
354    fn config(&self) -> &Self::Config {
355        &self.config
356    }
357
358    fn loaded(&self) -> &Self::Loaded {
359        &()
360    }
361}
362
363impl<C: EdwardsCurve> ComposableChip<C::Base> for EccChip<C> {
364    type SharedResources = [Column<Advice>; NB_EDWARDS_COLS];
365    type InstructionDeps = NG<C::Base>;
366
367    fn new(config: &Self::Config, sub_chips: &Self::InstructionDeps) -> Self {
368        Self {
369            config: config.clone(),
370            native_gadget: sub_chips.clone(),
371        }
372    }
373
374    fn configure(
375        meta: &mut ConstraintSystem<C::Base>,
376        advice_cols: &Self::SharedResources,
377    ) -> Self::Config {
378        assert_eq!(C::A, -C::Base::ONE);
379
380        // Only the first 7 need to be copy-enabled.
381        for col in advice_cols.iter().take(7) {
382            meta.enable_equality(*col)
383        }
384
385        let q_double = meta.selector();
386        let q_cond_add = meta.selector();
387        let q_mem = meta.selector();
388
389        let config = EccConfig {
390            q_double,
391            q_cond_add,
392            q_mem,
393            advice_cols: *advice_cols,
394        };
395
396        config.create_double_gate::<C>(meta, &q_double);
397        config.create_cond_add_gate::<C>(meta, &q_cond_add);
398        config.create_membership_gate::<C>(meta, &q_mem);
399
400        config
401    }
402
403    fn load(&self, _layouter: &mut impl Layouter<C::Base>) -> Result<(), Error> {
404        Ok(())
405    }
406}
407
408impl<C: EdwardsCurve> EccChip<C> {
409    /// Given `Q`, `S`, and bit `b`, supposedly already assigned in the
410    /// current row, this function assigns `R` in the same row and
411    /// enforces that `R = Q + b * S`.
412    //
413    // We use the following layout.
414    //
415    // ```text
416    //    0      1      2      3       4     5      6      7         8
417    // ----------------------------------------------------------------------
418    // |  xq  |  yq  |  xs  |  ys  |   b   | xr  |  yr  |     | xq_yq_xs_ys |
419    // ----------------------------------------------------------------------
420    // ```
421    fn assign_cond_add(
422        &self,
423        region: &mut Region<C::Base>,
424        offset: usize,
425        q: Value<C>,
426        s: Value<C>,
427        b: Value<bool>,
428    ) -> Result<AssignedNativePoint<C>, Error> {
429        let config = self.config();
430        config.q_cond_add.enable(region, offset)?;
431
432        let (xr_val, yr_val) = Self::p_plus_b_q(q, s, b);
433        let xr = region.assign_advice(|| "xr", config.advice_cols[5], offset, || xr_val)?;
434        let yr = region.assign_advice(|| "yr", config.advice_cols[6], offset, || yr_val)?;
435
436        let (xq, yq) = q.map(|q| q.coordinates().expect("non-id")).unzip();
437        let (xs, ys) = s.map(|s| s.coordinates().expect("non-id")).unzip();
438        let prod_val = xq * yq * xs * ys;
439        region.assign_advice(|| "xq_yq_xs_ys", config.advice_cols[8], offset, || prod_val)?;
440
441        Ok(AssignedNativePoint { x: xr, y: yr })
442    }
443
444    /// Given `P`, `Q`, and bit `b`, supposedly already assigned in the
445    /// current row, this function assigns `R` in the next row and
446    /// enforces that `R = 2 * (P + b * Q)`.
447    //
448    // We use the following layout.
449    //
450    // ```text
451    // ------------------------------------------------------------------------
452    // |  xp  |  yp  |  xq  |  yq  |  b   |  xs  |  ys  | xs_xs | xp_yp_xq_yq |
453    // |  xr  |  yr  |      |      |      |      |      |       |             |
454    // ------------------------------------------------------------------------
455    // ```
456    fn assign_add_then_double(
457        &self,
458        region: &mut Region<C::Base>,
459        offset: usize,
460        p_val: Value<C>,
461        q_val: Value<C>,
462        b_val: Value<bool>,
463    ) -> Result<AssignedNativePoint<C>, Error> {
464        let config = self.config();
465
466        config.q_cond_add.enable(region, offset)?;
467        config.q_double.enable(region, offset)?;
468
469        let (xs_val, ys_val) = Self::p_plus_b_q(p_val, q_val, b_val);
470
471        region.assign_advice(|| "xs", config.advice_cols[5], offset, || xs_val)?;
472        region.assign_advice(|| "ys", config.advice_cols[6], offset, || ys_val)?;
473
474        let s_val = xs_val.zip(ys_val).map(|(xs, ys)| C::from_xy(xs, ys).unwrap());
475        let r_val = s_val.map(|s| s + s);
476
477        let xr_val = r_val.map(|r: C| r.coordinates().expect("non-id").0);
478        let yr_val = r_val.map(|r: C| r.coordinates().expect("non-id").1);
479
480        let xr = region.assign_advice(|| "xr", config.advice_cols[0], offset + 1, || xr_val)?;
481        let yr = region.assign_advice(|| "yr", config.advice_cols[1], offset + 1, || yr_val)?;
482
483        region.assign_advice(
484            || "xs_xs",
485            config.advice_cols[7],
486            offset,
487            || xs_val * xs_val,
488        )?;
489
490        let (xp, yp) = p_val.map(|c| c.coordinates().expect("non-id")).unzip();
491        let (xq, yq) = q_val.map(|c| c.coordinates().expect("non-id")).unzip();
492        let prod_val = xp * yp * xq * yq;
493        region.assign_advice(|| "xp_yp_xq_yq", config.advice_cols[8], offset, || prod_val)?;
494
495        Ok(AssignedNativePoint { x: xr, y: yr })
496    }
497
498    /// Given the scalar in little-endian, double and add for each bit.
499    pub fn mul(
500        &self,
501        layouter: &mut impl Layouter<C::Base>,
502        scalar: &AssignedScalarOfNativeCurve<C>,
503        base: &AssignedNativePoint<C>,
504    ) -> Result<AssignedNativePoint<C>, Error> {
505        let config = &self.config();
506
507        // Convert to big-endian.
508        let scalar_be_bits = &mut scalar.0.clone();
509        scalar_be_bits.reverse();
510
511        let base_val = base.curve_value();
512        let id_point: AssignedNativePoint<C> =
513            self.assign_fixed(layouter, C::CryptographicGroup::identity())?;
514
515        layouter.assign_region(
516            || "assign mul",
517            |mut region: Region<'_, C::Base>| {
518                id_point.x.copy_advice(|| "id.x", &mut region, config.advice_cols[0], 0)?;
519                id_point.y.copy_advice(|| "id.y", &mut region, config.advice_cols[1], 0)?;
520
521                let mut acc = id_point.clone();
522
523                for (i, bit) in scalar_be_bits.iter().enumerate() {
524                    base.x.copy_advice(|| "base.x", &mut region, config.advice_cols[2], i)?;
525                    base.y.copy_advice(|| "base.y", &mut region, config.advice_cols[3], i)?;
526                    bit.0.copy_advice(|| "b cond_add", &mut region, config.advice_cols[4], i)?;
527
528                    if i < scalar_be_bits.len() - 1 {
529                        acc = self.assign_add_then_double(
530                            &mut region,
531                            i,
532                            acc.curve_value(),
533                            base_val,
534                            bit.value(),
535                        )?;
536                    }
537                    // In the last iteration, add but do not double.
538                    else {
539                        acc = self.assign_cond_add(
540                            &mut region,
541                            i,
542                            acc.curve_value(),
543                            base_val,
544                            bit.value(),
545                        )?;
546                    }
547                }
548
549                Ok(acc)
550            },
551        )
552    }
553
554    /// Given values of P, Q and b, computes the value of P + b * Q.
555    fn p_plus_b_q(p: Value<C>, q: Value<C>, b: Value<bool>) -> (Value<C::Base>, Value<C::Base>) {
556        p.zip(q)
557            .zip(b)
558            .map(|((p, q), b)| if b { p + q } else { p })
559            .map(|r| r.coordinates().expect("non-id"))
560            .unzip()
561    }
562
563    /// The native gadget carried by this chip.
564    pub fn native_gadget(&self) -> &impl NativeInstructions<C::Base> {
565        &self.native_gadget
566    }
567}
568
569impl<C: EdwardsCurve> EccInstructions<C::Base, C> for EccChip<C> {
570    type Point = AssignedNativePoint<C>;
571    type Coordinate = AssignedNative<C::Base>;
572    type Scalar = AssignedScalarOfNativeCurve<C>;
573
574    fn add(
575        &self,
576        layouter: &mut impl Layouter<C::Base>,
577        p: &Self::Point,
578        q: &Self::Point,
579    ) -> Result<Self::Point, Error> {
580        let config = self.config();
581        let b: AssignedBit<C::Base> = self.native_gadget.assign_fixed(layouter, true)?;
582
583        layouter.assign_region(
584            || "assign add",
585            |mut region: Region<'_, C::Base>| {
586                p.x.copy_advice(|| "px", &mut region, config.advice_cols[0], 0)?;
587                p.y.copy_advice(|| "py", &mut region, config.advice_cols[1], 0)?;
588                q.x.copy_advice(|| "qx", &mut region, config.advice_cols[2], 0)?;
589                q.y.copy_advice(|| "qy", &mut region, config.advice_cols[3], 0)?;
590                b.0.copy_advice(|| "b", &mut region, config.advice_cols[4], 0)?;
591
592                self.assign_cond_add(&mut region, 0, p.curve_value(), q.curve_value(), b.value())
593            },
594        )
595    }
596
597    fn double(
598        &self,
599        layouter: &mut impl Layouter<C::Base>,
600        p: &Self::Point,
601    ) -> Result<Self::Point, Error> {
602        self.add(layouter, p, p)
603    }
604
605    fn negate(
606        &self,
607        layouter: &mut impl Layouter<C::Base>,
608        p: &Self::Point,
609    ) -> Result<Self::Point, Error> {
610        Ok(AssignedNativePoint {
611            x: self.native_gadget.neg(layouter, &p.x)?,
612            y: p.y.clone(),
613        })
614    }
615
616    fn msm(
617        &self,
618        layouter: &mut impl Layouter<C::Base>,
619        scalars: &[Self::Scalar],
620        bases: &[Self::Point],
621    ) -> Result<Self::Point, Error> {
622        let scaled_points = scalars
623            .iter()
624            .zip(bases.iter())
625            .map(|(scalar, point)| self.mul(layouter, scalar, point))
626            .collect::<Result<Vec<Self::Point>, Error>>()?;
627
628        scaled_points[1..].iter().try_fold(scaled_points[0].clone(), |acc, e| {
629            self.add(layouter, &acc, e)
630        })
631    }
632
633    fn mul_by_constant(
634        &self,
635        layouter: &mut impl Layouter<C::Base>,
636        scalar: C::Scalar,
637        base: &Self::Point,
638    ) -> Result<Self::Point, Error> {
639        if scalar == C::Scalar::ZERO {
640            return self.assign_fixed(layouter, C::CryptographicGroup::identity());
641        }
642
643        if scalar == C::Scalar::ONE {
644            return Ok(base.clone());
645        }
646
647        let s = self.assign_fixed(layouter, scalar)?;
648        self.msm(layouter, &[s], &[base.clone()])
649    }
650
651    fn point_from_coordinates(
652        &self,
653        layouter: &mut impl Layouter<C::Base>,
654        x: &Self::Coordinate,
655        y: &Self::Coordinate,
656    ) -> Result<Self::Point, Error> {
657        layouter.assign_region(
658            || "assign new point",
659            |mut region: Region<'_, C::Base>| {
660                x.copy_advice(|| "x", &mut region, self.config.advice_cols[0], 0)?;
661                y.copy_advice(|| "y", &mut region, self.config.advice_cols[1], 0)?;
662                self.config.q_mem.enable(&mut region, 0)
663            },
664        )?;
665        Ok(AssignedNativePoint {
666            x: x.clone(),
667            y: y.clone(),
668        })
669    }
670
671    fn x_coordinate(&self, point: &Self::Point) -> Self::Coordinate {
672        point.x.clone()
673    }
674
675    fn y_coordinate(&self, point: &Self::Point) -> Self::Coordinate {
676        point.y.clone()
677    }
678
679    fn base_field(&self) -> &impl DecompositionInstructions<C::Base, Self::Coordinate> {
680        &self.native_gadget
681    }
682}
683
684impl<C: EdwardsCurve> AssignmentInstructions<C::Base, AssignedNativePoint<C>> for EccChip<C> {
685    fn assign(
686        &self,
687        layouter: &mut impl Layouter<C::Base>,
688        value: Value<C::CryptographicGroup>,
689    ) -> Result<AssignedNativePoint<C>, Error> {
690        let config = self.config();
691
692        // Ensure the point lies in the correct subgroup.
693        // To achieve this, we first assign the point multiplied by the inverse of the
694        // cofactor. Then, we return the assigned point after multiplying it by
695        // the cofactor.
696        let cofactor = C::Scalar::from_u128(C::COFACTOR);
697        let (x_val, y_val) = value
698            .map(|p| {
699                let p = p * cofactor.invert().expect("cofactor should not be 0");
700                p.into().coordinates().expect("non-id")
701            })
702            .unzip();
703
704        let cf_root = layouter.assign_region(
705            || "assign point",
706            |mut region: Region<'_, C::Base>| {
707                config.q_mem.enable(&mut region, 0)?;
708                let x = region.assign_advice(|| "x", config.advice_cols[0], 0, || x_val)?;
709                let y = region.assign_advice(|| "y", config.advice_cols[1], 0, || y_val)?;
710                Ok(AssignedNativePoint { x, y })
711            },
712        )?;
713
714        self.mul_by_constant(layouter, cofactor, &cf_root)
715    }
716
717    fn assign_fixed(
718        &self,
719        layouter: &mut impl Layouter<C::Base>,
720        constant: C::CryptographicGroup,
721    ) -> Result<AssignedNativePoint<C>, Error> {
722        let coords = constant.into().coordinates().expect("non-id");
723        let x = self.native_gadget.assign_fixed(layouter, coords.0)?;
724        let y = self.native_gadget.assign_fixed(layouter, coords.1)?;
725        Ok(AssignedNativePoint { x, y })
726    }
727}
728
729impl<C: EdwardsCurve> AssignmentInstructions<C::Base, AssignedScalarOfNativeCurve<C>>
730    for EccChip<C>
731{
732    fn assign(
733        &self,
734        layouter: &mut impl Layouter<C::Base>,
735        value: Value<C::Scalar>,
736    ) -> Result<AssignedScalarOfNativeCurve<C>, Error> {
737        let bits = value
738            .map(|s| fe_to_le_bits(&s, Some(C::Scalar::NUM_BITS as usize)))
739            .transpose_vec(<C::Scalar as PrimeField>::NUM_BITS as usize);
740        self.native_gadget.assign_many(layouter, &bits).map(AssignedScalarOfNativeCurve)
741    }
742
743    fn assign_fixed(
744        &self,
745        layouter: &mut impl Layouter<C::Base>,
746        constant: C::Scalar,
747    ) -> Result<AssignedScalarOfNativeCurve<C>, Error> {
748        self.native_gadget
749            .assign_many_fixed(layouter, &fe_to_le_bits(&constant, None))
750            .map(AssignedScalarOfNativeCurve)
751    }
752}
753
754impl<C: EdwardsCurve> AssertionInstructions<C::Base, AssignedNativePoint<C>> for EccChip<C> {
755    fn assert_equal(
756        &self,
757        layouter: &mut impl Layouter<C::Base>,
758        p: &AssignedNativePoint<C>,
759        q: &AssignedNativePoint<C>,
760    ) -> Result<(), Error> {
761        self.native_gadget.assert_equal(layouter, &p.x, &q.x)?;
762        self.native_gadget.assert_equal(layouter, &p.y, &q.y)
763    }
764
765    fn assert_not_equal(
766        &self,
767        layouter: &mut impl Layouter<C::Base>,
768        p: &AssignedNativePoint<C>,
769        q: &AssignedNativePoint<C>,
770    ) -> Result<(), Error> {
771        let is_eq = self.is_equal(layouter, p, q)?;
772        self.native_gadget.assert_equal_to_fixed(layouter, &is_eq, false)
773    }
774
775    fn assert_equal_to_fixed(
776        &self,
777        layouter: &mut impl Layouter<C::Base>,
778        p: &AssignedNativePoint<C>,
779        constant: C::CryptographicGroup,
780    ) -> Result<(), Error> {
781        let (cx, cy) = constant.into().coordinates().expect("non-id");
782        self.native_gadget.assert_equal_to_fixed(layouter, &p.x, cx)?;
783        self.native_gadget.assert_equal_to_fixed(layouter, &p.y, cy)
784    }
785
786    fn assert_not_equal_to_fixed(
787        &self,
788        layouter: &mut impl Layouter<C::Base>,
789        p: &AssignedNativePoint<C>,
790        constant: C::CryptographicGroup,
791    ) -> Result<(), Error> {
792        let is_eq = self.is_equal_to_fixed(layouter, p, constant)?;
793        self.native_gadget.assert_equal_to_fixed(layouter, &is_eq, false)
794    }
795}
796
797impl<C: EdwardsCurve> PublicInputInstructions<C::Base, AssignedNativePoint<C>> for EccChip<C> {
798    fn as_public_input(
799        &self,
800        _layouter: &mut impl Layouter<C::Base>,
801        p: &AssignedNativePoint<C>,
802    ) -> Result<Vec<AssignedNative<C::Base>>, Error> {
803        Ok(vec![p.x.clone(), p.y.clone()])
804    }
805
806    fn constrain_as_public_input(
807        &self,
808        layouter: &mut impl Layouter<C::Base>,
809        p: &AssignedNativePoint<C>,
810    ) -> Result<(), Error> {
811        self.as_public_input(layouter, p)?
812            .iter()
813            .try_for_each(|c| self.native_gadget.constrain_as_public_input(layouter, c))
814    }
815
816    fn assign_as_public_input(
817        &self,
818        layouter: &mut impl Layouter<C::Base>,
819        p: Value<C::CryptographicGroup>,
820    ) -> Result<AssignedNativePoint<C>, Error> {
821        // We can skip the curve equation check in this case.
822        let (x, y) = p.map(|p| p.into().coordinates().expect("non-id")).unzip();
823        let x = self.native_gadget.assign_as_public_input(layouter, x)?;
824        let y = self.native_gadget.assign_as_public_input(layouter, y)?;
825        Ok(AssignedNativePoint { x, y })
826    }
827}
828
829impl<C: EdwardsCurve> PublicInputInstructions<C::Base, AssignedScalarOfNativeCurve<C>>
830    for EccChip<C>
831{
832    fn as_public_input(
833        &self,
834        layouter: &mut impl Layouter<C::Base>,
835        assigned: &AssignedScalarOfNativeCurve<C>,
836    ) -> Result<Vec<AssignedNative<C::Base>>, Error> {
837        // We aggregate the bits while they fit in a single `AssignedNative`.
838        let nb_bits_per_batch = C::Base::NUM_BITS as usize - 1;
839        assigned
840            .0
841            .chunks(nb_bits_per_batch)
842            .map(|chunk| self.native_gadget.assigned_from_le_bits(layouter, chunk))
843            .collect()
844    }
845
846    fn constrain_as_public_input(
847        &self,
848        layouter: &mut impl Layouter<C::Base>,
849        assigned: &AssignedScalarOfNativeCurve<C>,
850    ) -> Result<(), Error> {
851        self.as_public_input(layouter, assigned)?
852            .iter()
853            .try_for_each(|c| self.native_gadget.constrain_as_public_input(layouter, c))
854    }
855
856    fn assign_as_public_input(
857        &self,
858        layouter: &mut impl Layouter<C::Base>,
859        value: Value<C::Scalar>,
860    ) -> Result<AssignedScalarOfNativeCurve<C>, Error> {
861        let assigned: AssignedScalarOfNativeCurve<C> = self.assign(layouter, value)?;
862        self.constrain_as_public_input(layouter, &assigned)?;
863        Ok(assigned)
864    }
865}
866
867impl<C: EdwardsCurve> EqualityInstructions<C::Base, AssignedNativePoint<C>> for EccChip<C> {
868    fn is_equal(
869        &self,
870        layouter: &mut impl Layouter<C::Base>,
871        p: &AssignedNativePoint<C>,
872        q: &AssignedNativePoint<C>,
873    ) -> Result<AssignedBit<C::Base>, Error> {
874        let eq_x = self.native_gadget.is_equal(layouter, &p.x, &q.x)?;
875        let eq_y = self.native_gadget.is_equal(layouter, &p.y, &q.y)?;
876        self.native_gadget.and(layouter, &[eq_x, eq_y])
877    }
878
879    fn is_not_equal(
880        &self,
881        layouter: &mut impl Layouter<C::Base>,
882        p: &AssignedNativePoint<C>,
883        q: &AssignedNativePoint<C>,
884    ) -> Result<AssignedBit<C::Base>, Error> {
885        let not_eq_x = self.native_gadget.is_not_equal(layouter, &p.x, &q.x)?;
886        let not_eq_y = self.native_gadget.is_not_equal(layouter, &p.y, &q.y)?;
887        self.native_gadget.or(layouter, &[not_eq_x, not_eq_y])
888    }
889
890    fn is_equal_to_fixed(
891        &self,
892        layouter: &mut impl Layouter<C::Base>,
893        p: &AssignedNativePoint<C>,
894        constant: C::CryptographicGroup,
895    ) -> Result<AssignedBit<C::Base>, Error> {
896        let (cx, cy) = constant.into().coordinates().expect("non-id");
897        let eq_x = self.native_gadget.is_equal_to_fixed(layouter, &p.x, cx)?;
898        let eq_y = self.native_gadget.is_equal_to_fixed(layouter, &p.y, cy)?;
899        self.native_gadget.and(layouter, &[eq_x, eq_y])
900    }
901
902    fn is_not_equal_to_fixed(
903        &self,
904        layouter: &mut impl Layouter<C::Base>,
905        p: &AssignedNativePoint<C>,
906        constant: C::CryptographicGroup,
907    ) -> Result<AssignedBit<C::Base>, Error> {
908        let (cx, cy) = constant.into().coordinates().expect("non-id");
909        let not_eq_x = self.native_gadget.is_not_equal_to_fixed(layouter, &p.x, cx)?;
910        let not_eq_y = self.native_gadget.is_not_equal_to_fixed(layouter, &p.y, cy)?;
911        self.native_gadget.or(layouter, &[not_eq_x, not_eq_y])
912    }
913}
914
915impl<C: EdwardsCurve> ZeroInstructions<C::Base, AssignedNativePoint<C>> for EccChip<C> {}
916
917impl<C: EdwardsCurve> ControlFlowInstructions<C::Base, AssignedNativePoint<C>> for EccChip<C> {
918    fn select(
919        &self,
920        layouter: &mut impl Layouter<C::Base>,
921        cond: &AssignedBit<C::Base>,
922        a: &AssignedNativePoint<C>,
923        b: &AssignedNativePoint<C>,
924    ) -> Result<AssignedNativePoint<C>, Error> {
925        let x = self.native_gadget.select(layouter, cond, &a.x, &b.x)?;
926        let y = self.native_gadget.select(layouter, cond, &a.y, &b.y)?;
927        Ok(AssignedNativePoint { x, y })
928    }
929}
930
931#[cfg(any(test, feature = "testing"))]
932impl<C: EdwardsCurve> FromScratch<C::Base> for EccChip<C> {
933    type Config = (EccConfig, P2RDecompositionConfig);
934
935    fn new_from_scratch(config: &Self::Config) -> Self {
936        let p2r_decomp_config = &config.1;
937        let max_bit_len = 8;
938        let native_chip = NativeChip::new_from_scratch(&p2r_decomp_config.native_config);
939        let core_decomposition_chip = P2RDecompositionChip::new(p2r_decomp_config, &max_bit_len);
940        let native_gadget = NativeGadget::new(core_decomposition_chip, native_chip);
941        Self {
942            native_gadget,
943            config: config.0.clone(),
944        }
945    }
946
947    fn configure_from_scratch(
948        meta: &mut ConstraintSystem<C::Base>,
949        instance_columns: &[Column<Instance>; 2],
950    ) -> Self::Config {
951        let native_gadget_config =
952            <NG<C::Base> as FromScratch<C::Base>>::configure_from_scratch(meta, instance_columns);
953        let advice_cols: [Column<Advice>; NB_EDWARDS_COLS] =
954            core::array::from_fn(|_| meta.advice_column());
955        let ecc_config = EccChip::<C>::configure(meta, &advice_cols);
956
957        (ecc_config, native_gadget_config)
958    }
959
960    fn load_from_scratch(&self, layouter: &mut impl Layouter<C::Base>) -> Result<(), Error> {
961        self.native_gadget.load_from_scratch(layouter)
962    }
963}
964
965#[cfg(any(test, feature = "testing"))]
966impl<C: EdwardsCurve> Sampleable for AssignedNativePoint<C> {
967    fn sample_inner(rng: impl RngCore) -> C::CryptographicGroup {
968        C::CryptographicGroup::random(rng)
969    }
970}
971
972impl<C: EdwardsCurve> EccChip<C> {
973    /// Creates an assigned Jubjub scalar from an integer represented as a
974    /// sequence of little-endian bytes.
975    pub fn scalar_from_le_bytes(
976        &self,
977        layouter: &mut impl Layouter<C::Base>,
978        bytes: &[AssignedByte<C::Base>],
979    ) -> Result<AssignedScalarOfNativeCurve<C>, Error> {
980        let mut bits = Vec::with_capacity(bytes.len() * 8);
981        for byte in bytes {
982            let byte_as_f: AssignedNative<C::Base> = self.native_gadget.convert(layouter, byte)?;
983            bits.extend(self.native_gadget.assigned_to_le_bits(
984                layouter,
985                &byte_as_f,
986                Some(8),
987                true,
988            )?)
989        }
990        Ok(AssignedScalarOfNativeCurve(bits))
991    }
992
993    /// Creates an assigned Jubjub scalar from an integer represented as a
994    /// sequence of little-endian bytes.
995    ///
996    /// # Unsatisfiable
997    ///
998    /// The circuit becomes unsatisfiable if bytes represent an integer larger
999    /// than or equal to 2^C::NUM_BITS_SUBGROUP.
1000    ///
1001    /// NB: In the case of Jubjub, NUM_BITS_SUBGROUP equals 252.
1002    pub fn scalar_from_reduced_le_bytes(
1003        &self,
1004        layouter: &mut impl Layouter<C::Base>,
1005        bytes: &[AssignedByte<C::Base>],
1006    ) -> Result<AssignedScalarOfNativeCurve<C>, Error> {
1007        let n = C::NUM_BITS_SUBGROUP as usize;
1008        let s = self.scalar_from_le_bytes(layouter, bytes)?;
1009        for b in s.0[n..].iter() {
1010            self.native_gadget.assert_equal_to_fixed(layouter, b, false)?;
1011        }
1012        Ok(AssignedScalarOfNativeCurve(s.0[..n].to_vec()))
1013    }
1014}
1015
1016/// This conversion should not exist for Base -> Scalar. It is a tech debt. We
1017/// should fix this as soon as compact supports types (other than assigned
1018/// native) <https://github.com/midnightntwrk/midnight-circuits/issues/433>
1019impl<C: EdwardsCurve>
1020    ConversionInstructions<C::Base, AssignedNative<C::Base>, AssignedScalarOfNativeCurve<C>>
1021    for EccChip<C>
1022{
1023    fn convert_value(&self, _x: &C::Base) -> Option<C::Scalar> {
1024        unimplemented!("The caller should decide how to convert the value off-circuit, i.e., what to do with overflows.");
1025    }
1026
1027    fn convert(
1028        &self,
1029        layouter: &mut impl Layouter<C::Base>,
1030        x: &AssignedNative<C::Base>,
1031    ) -> Result<AssignedScalarOfNativeCurve<C>, Error> {
1032        Ok(AssignedScalarOfNativeCurve(
1033            self.native_gadget.assigned_to_le_bits(layouter, x, None, true)?,
1034        ))
1035    }
1036}
1037
1038#[cfg(test)]
1039mod tests {
1040    use midnight_curves::{Fq as JubjubBase, JubjubExtended};
1041
1042    use super::*;
1043    use crate::{
1044        ecc::hash_to_curve::HashToCurveGadget,
1045        hash::poseidon::PoseidonChip,
1046        instructions::{ecc, hash_to_curve::tests::test_hash_to_curve},
1047    };
1048
1049    macro_rules! test_generic {
1050        ($mod:ident, $op:ident, $native:ty, $curve:ty, $name:expr) => {
1051            $mod::tests::$op::<$native, AssignedNativePoint<$curve>, EccChip<$curve>>($name);
1052        };
1053    }
1054
1055    macro_rules! test {
1056        ($mod:ident, $op:ident) => {
1057            #[test]
1058            fn $op() {
1059                test_generic!($mod, $op, JubjubBase, JubjubExtended, "native_ecc");
1060            }
1061        };
1062    }
1063
1064    test!(assertions, test_assertions);
1065
1066    test!(public_input, test_public_inputs);
1067
1068    #[test]
1069    fn test_scalarvar_public_inputs() {
1070        public_input::tests::test_public_inputs::<
1071            JubjubBase,
1072            AssignedScalarOfNativeCurve<JubjubExtended>,
1073            EccChip<JubjubExtended>,
1074        >("public_inputs_scalar_var");
1075    }
1076
1077    test!(equality, test_is_equal);
1078
1079    test!(zero, test_zero_assertions);
1080    test!(zero, test_is_zero);
1081
1082    test!(control_flow, test_select);
1083    test!(control_flow, test_cond_assert_equal);
1084    test!(control_flow, test_cond_swap);
1085
1086    macro_rules! ecc_tests {
1087        ($op:ident) => {
1088            #[test]
1089            fn $op() {
1090                ecc::tests::$op::<JubjubBase, JubjubExtended, EccChip<JubjubExtended>>(
1091                    "native_ecc",
1092                );
1093            }
1094        };
1095    }
1096
1097    ecc_tests!(test_add);
1098    ecc_tests!(test_double);
1099    ecc_tests!(test_negate);
1100    ecc_tests!(test_msm);
1101    ecc_tests!(test_msm_by_bounded_scalars);
1102    ecc_tests!(test_mul_by_constant);
1103    ecc_tests!(test_coordinates_edwards);
1104
1105    #[test]
1106    fn test_htc() {
1107        test_hash_to_curve::<
1108            JubjubBase,
1109            JubjubExtended,
1110            AssignedNative<JubjubBase>,
1111            EccChip<JubjubExtended>,
1112            NativeChip<JubjubBase>,
1113            HashToCurveGadget<_, _, _, PoseidonChip<JubjubBase>, _>,
1114        >("native_ecc")
1115    }
1116}