Skip to main content

midnight_circuits/ecc/native/
edwards_chip.rs

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