Skip to main content

midnight_circuits/verifier/
msm.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//! A module for in-circuit partial MSMs and its off-circuit analog.
15//! These MSM have a fixed-base part which is represented by the corresponding
16//! scalars only.
17//! (The bases are assumed to be fixed and globally known.)
18
19use std::collections::{btree_map::Entry, BTreeMap};
20
21use ff::Field;
22use midnight_curves::msm::msm_best;
23use midnight_proofs::{
24    circuit::{Layouter, Value},
25    plonk::Error,
26};
27
28use crate::{
29    field::AssignedNative,
30    instructions::PublicInputInstructions,
31    types::{InnerValue, Instantiable},
32    verifier::{
33        types::SelfEmulation,
34        utils::{
35            add_bounded_scalars, assign_bounded_scalars, mul_bounded_scalars, AssignedBoundedScalar,
36        },
37    },
38};
39
40/// Type for off-circuit multi-scalar multiplications.
41///
42/// This structure represents the following computation:
43/// `<scalars, bases> + <fixed_bases, fixed_base_scalars>`
44///
45/// Note that the `fixed_bases` are not part of this structure, they are
46/// supposed to be globally known and will be provided when evaluating the MSM.
47///
48/// (`scalars` and `bases` are guaranteed to have the same length.)
49#[derive(Clone, Debug)]
50pub struct Msm<S: SelfEmulation> {
51    bases: Vec<S::C>,
52    scalars: Vec<S::F>,
53    fixed_base_scalars: BTreeMap<String, S::F>,
54}
55
56/// Type for in-circuit multi-scalar multiplications.
57///
58/// This is the in-circuit analog of `Msm<C>`.
59#[derive(Clone, Debug)]
60pub struct AssignedMsm<S: SelfEmulation> {
61    bases: Vec<S::AssignedPoint>,
62    pub(crate) scalars: Vec<AssignedBoundedScalar<S::F>>,
63    fixed_base_scalars: BTreeMap<String, AssignedBoundedScalar<S::F>>,
64}
65
66impl<S: SelfEmulation> PartialEq for AssignedMsm<S> {
67    fn eq(&self, other: &Self) -> bool {
68        self.bases == other.bases
69            && self.scalars == other.scalars
70            && self.fixed_base_scalars == other.fixed_base_scalars
71    }
72}
73
74impl<S: SelfEmulation> Eq for AssignedMsm<S> {}
75
76impl<S: SelfEmulation> Msm<S> {
77    /// Creates a new MSM from the given slice of bases, scalars and a BTreeMap
78    /// of fixed_base_scalars.
79    ///
80    /// # Panics
81    ///
82    /// If `|bases| != |scalars|`.
83    pub fn new(
84        bases: &[S::C],
85        scalars: &[S::F],
86        fixed_base_scalars: &BTreeMap<String, S::F>,
87    ) -> Self {
88        assert_eq!(bases.len(), scalars.len());
89        Msm {
90            bases: bases.to_vec(),
91            scalars: scalars.to_vec(),
92            fixed_base_scalars: fixed_base_scalars.clone(),
93        }
94    }
95
96    /// The bases of this MSM.
97    pub fn bases(&self) -> Vec<S::C> {
98        self.bases.clone()
99    }
100
101    /// The (non-fixed-base) scalars of this MSM.
102    pub fn scalars(&self) -> Vec<S::F> {
103        self.scalars.clone()
104    }
105
106    /// The fixed-base scalars of this MSM.
107    pub fn fixed_base_scalars(&self) -> BTreeMap<String, S::F> {
108        self.fixed_base_scalars.clone()
109    }
110
111    /// Creates a new MSM from the given base-scalar pairs, with an empty tree
112    /// of fixed_base_scalars.
113    ///
114    /// # Panics
115    ///
116    /// If `|bases| != |scalars|`.
117    pub fn from_terms(bases: &[S::C], scalars: &[S::F]) -> Self {
118        assert_eq!(bases.len(), scalars.len());
119        Msm {
120            bases: bases.to_vec(),
121            scalars: scalars.to_vec(),
122            fixed_base_scalars: BTreeMap::new(),
123        }
124    }
125
126    /// Evaluates the variable part of the AssignedMsm (the scalar-base pairs)
127    /// collapsing it to a single point (and a scalar of 1), leaving the
128    /// fixed-base part intact.
129    ///
130    /// This function mutates self.
131    pub fn collapse(&mut self) {
132        let affine_bases: Vec<S::G1Affine> = self.bases.iter().map(|&b| b.into()).collect();
133        let collapsed_base = msm_best(&self.scalars, &affine_bases);
134
135        self.bases = vec![collapsed_base];
136        self.scalars = vec![S::F::ONE];
137    }
138
139    /// Given the actual fixed bases, resolves the fixed-base part of the MSM
140    /// by pairing each named scalar with its base and moving them to regular
141    /// variable-base entries.
142    ///
143    /// After this call, `fixed_base_scalars` becomes empty.
144    ///
145    /// # Panics
146    ///
147    /// If some of the keys in `fixed_base_scalars` do not appear in the
148    /// provided `fixed_bases` map.
149    pub fn resolve_fixed_bases(&mut self, fixed_bases: &BTreeMap<String, S::C>) {
150        for (name, scalar) in &self.fixed_base_scalars {
151            let base = fixed_bases.get(name).unwrap_or_else(|| panic!("Base not provided: {name}"));
152            self.bases.push(*base);
153            self.scalars.push(*scalar);
154        }
155        self.fixed_base_scalars.clear();
156    }
157
158    /// Evaluates the MSM with the provided fixed_bases.
159    /// I.e. it computes `<scalars, bases> + <fixed_bases, fixed_base_scalars>`.
160    ///
161    /// # Panics
162    ///
163    /// If some of the keys in the `fixed_base_scalars` of the MSM do not appear
164    /// in the tree of `fixed_bases`.
165    ///
166    /// Note that the converse is not a problem, i.e., the keys of `fixed_bases`
167    /// can be a superset of the keys of `fixed_base_scalars`.
168    pub fn eval(&self, fixed_bases: &BTreeMap<String, S::C>) -> S::C {
169        let mut bases = self.bases.clone();
170        let mut scalars = self.scalars.clone();
171
172        for (key, scalar) in self.fixed_base_scalars.iter() {
173            let base = fixed_bases.get(key).unwrap_or_else(|| panic!("Base not provided: {key}"));
174            bases.push(*base);
175            scalars.push(*scalar);
176        }
177
178        let affine_bases: Vec<S::G1Affine> = bases.iter().map(|&b| b.into()).collect();
179        msm_best(&scalars, &affine_bases)
180    }
181
182    /// Accumulates two MSMs with the given scalar r.
183    /// The resulting MSM evaluates (on any `fixed_bases`) to
184    /// `self.eval(fixed_bases) + r * other.eval(fixed_bases)`.
185    pub fn accumulate_with_r(&self, other: &Self, r: S::F) -> Self {
186        let mut acc = self.clone();
187
188        acc.bases.extend(other.bases.clone());
189        acc.scalars.extend(other.scalars.iter().map(|s| *s * r));
190
191        for (key, value) in other.fixed_base_scalars.clone() {
192            let r_times_value = r * value;
193            acc.fixed_base_scalars
194                .entry(key)
195                .and_modify(|e| *e += r_times_value)
196                .or_insert(r_times_value);
197        }
198
199        acc
200    }
201}
202
203impl<S: SelfEmulation> InnerValue for AssignedMsm<S> {
204    type Element = Msm<S>;
205
206    fn value(&self) -> Value<Self::Element> {
207        let bases: Value<Vec<S::C>> = Value::from_iter(self.bases.iter().map(|base| base.value()));
208
209        let scalars: Value<Vec<S::F>> =
210            Value::from_iter(self.scalars.iter().map(|s| s.scalar.value().copied()));
211
212        let fixed_based_scalars: Value<BTreeMap<String, S::F>> = Value::from_iter(
213            self.fixed_base_scalars
214                .iter()
215                .map(|(name, s)| s.scalar.value().map(|s| (name.clone(), *s))),
216        );
217
218        scalars
219            .zip(bases)
220            .zip(fixed_based_scalars)
221            .map(|((scalars, bases), fixed_base_scalars)| Msm {
222                bases,
223                scalars,
224                fixed_base_scalars,
225            })
226    }
227}
228
229impl<S: SelfEmulation> Instantiable<S::F> for AssignedMsm<S> {
230    fn as_public_input(msm: &Msm<S>) -> Vec<S::F> {
231        [
232            msm.bases.iter().flat_map(S::AssignedPoint::as_public_input).collect::<Vec<_>>(),
233            msm.scalars.clone(),
234            msm.fixed_base_scalars.values().copied().collect::<Vec<_>>(),
235        ]
236        .into_iter()
237        .flatten()
238        .collect::<Vec<_>>()
239    }
240
241    fn from_public_input(_fields: &[S::F]) -> Option<Msm<S>> {
242        unimplemented!("not invertible: the flat encoding loses structural metadata.")
243    }
244}
245
246impl<S: SelfEmulation> AssignedMsm<S> {
247    /// Converts the off-circuit MSM into two vectors of scalars. The first
248    /// will be used as a normal instance, whereas the second will be plugged-in
249    /// in as a committed instance.
250    ///
251    /// The committed instance part corresponds to the (fixed and non-fixed)
252    /// scalars of the MSM.
253    pub fn as_public_input_with_committed_scalars(msm: &Msm<S>) -> (Vec<S::F>, Vec<S::F>) {
254        let normal_instance =
255            msm.bases.iter().flat_map(S::AssignedPoint::as_public_input).collect();
256
257        let committed_instance = [
258            msm.scalars.clone(),
259            msm.fixed_base_scalars.values().copied().collect(),
260        ]
261        .concat();
262
263        (normal_instance, committed_instance)
264    }
265}
266
267impl<S: SelfEmulation> AssignedMsm<S> {
268    pub(crate) fn in_circuit_as_public_input(
269        &self,
270        layouter: &mut impl Layouter<S::F>,
271        curve_chip: &S::CurveChip,
272    ) -> Result<Vec<AssignedNative<S::F>>, Error> {
273        Ok([
274            self.bases
275                .iter()
276                .map(|base| curve_chip.as_public_input(layouter, base))
277                .collect::<Result<Vec<_>, Error>>()?
278                .into_iter()
279                .flatten()
280                .collect::<Vec<_>>(),
281            self.scalars.iter().map(|s| s.clone().scalar).collect::<Vec<_>>(),
282            self.fixed_base_scalars.values().map(|s| s.clone().scalar).collect::<Vec<_>>(),
283        ]
284        .into_iter()
285        .flatten()
286        .collect())
287    }
288
289    pub(crate) fn constrain_as_public_input(
290        &self,
291        layouter: &mut impl Layouter<S::F>,
292        curve_chip: &S::CurveChip,
293        scalar_chip: &S::ScalarChip,
294    ) -> Result<(), Error> {
295        self.bases
296            .iter()
297            .try_for_each(|base| curve_chip.constrain_as_public_input(layouter, base))?;
298
299        self.scalars
300            .iter()
301            .try_for_each(|s| scalar_chip.constrain_as_public_input(layouter, &s.clone().scalar))?;
302
303        self.fixed_base_scalars
304            .values()
305            .try_for_each(|s| scalar_chip.constrain_as_public_input(layouter, &s.clone().scalar))
306    }
307
308    pub(crate) fn constrain_as_public_input_with_committed_scalars(
309        &self,
310        layouter: &mut impl Layouter<S::F>,
311        curve_chip: &S::CurveChip,
312        scalar_chip: &S::ScalarChip,
313    ) -> Result<(), Error> {
314        self.bases
315            .iter()
316            .try_for_each(|base| curve_chip.constrain_as_public_input(layouter, base))?;
317
318        self.scalars.iter().try_for_each(|s| {
319            let mut a = S::F::ZERO;
320            s.scalar.clone().value().map(|v| a = *v);
321            S::constrain_scalar_as_committed_public_input(layouter, scalar_chip, &s.scalar)
322        })?;
323
324        self.fixed_base_scalars.values().try_for_each(|s| {
325            S::constrain_scalar_as_committed_public_input(layouter, scalar_chip, &s.scalar)
326        })
327    }
328}
329
330impl<S: SelfEmulation> AssignedMsm<S> {
331    /// Witnesses an MSM computation of `len` bases/scalars and a `BTreeMap` of
332    /// fixed_base_scalars indexed by the given `fixed_base_names`.
333    ///
334    /// # Warning
335    ///
336    /// The points of the MSM are not enforced to be part of the relevant prime
337    /// order subgroup.
338    ///
339    /// # Panics
340    ///
341    /// If `msm_value` is known and its number of variable bases differs from
342    /// `len`, or its number of fixed-base scalars differs from
343    /// `fixed_base_names.len()`.
344    pub fn assign(
345        layouter: &mut impl Layouter<S::F>,
346        curve_chip: &S::CurveChip,
347        scalar_chip: &S::ScalarChip,
348        len: usize,
349        fixed_base_names: &[String],
350        msm_value: Value<Msm<S>>,
351    ) -> Result<Self, Error> {
352        let bases_val = msm_value.as_ref().map(|msm| msm.bases.clone()).transpose_vec(len);
353
354        let scalars_val = msm_value.as_ref().map(|msm| msm.scalars.clone()).transpose_vec(len);
355
356        let fixed_base_scalars_val = msm_value
357            .as_ref()
358            .map(|msm| {
359                // We only use the keys inside the Value to iterate over it in the right order,
360                // these are then discarded.
361                msm.fixed_base_scalars.iter().map(|s| *s.1).collect::<Vec<_>>()
362            })
363            .transpose_vec(fixed_base_names.len());
364
365        // Sort the fixed_base_names to ensure consistency with the BTreeMap.
366        let mut fixed_base_names = fixed_base_names.to_vec();
367        fixed_base_names.sort();
368
369        let bases = bases_val
370            .iter()
371            .map(|p| S::assign_without_subgroup_check(layouter, curve_chip, *p))
372            .collect::<Result<Vec<_>, Error>>()?;
373        let scalars = assign_bounded_scalars(layouter, scalar_chip, &scalars_val)?;
374        let fixed_base_scalars: BTreeMap<String, AssignedBoundedScalar<S::F>> = {
375            let scalars = assign_bounded_scalars(layouter, scalar_chip, &fixed_base_scalars_val)?;
376            fixed_base_names.iter().cloned().zip(scalars).collect()
377        };
378
379        Ok(AssignedMsm {
380            scalars,
381            bases,
382            fixed_base_scalars,
383        })
384    }
385
386    /// An empty AssignedMsm with no fixed base scalars, that evaluates to the
387    /// identity point.
388    pub fn empty() -> Self {
389        Self {
390            scalars: vec![],
391            bases: vec![],
392            fixed_base_scalars: BTreeMap::new(),
393        }
394    }
395
396    /// Creates a new MSM from the given base (with a scalar of 1).
397    pub fn from_term(scalar: &AssignedBoundedScalar<S::F>, base: &S::AssignedPoint) -> Self {
398        Self {
399            scalars: vec![scalar.clone()],
400            bases: vec![base.clone()],
401            fixed_base_scalars: BTreeMap::new(),
402        }
403    }
404
405    /// Creates a new MSM from the given fixed base name (with a scalar of 1).
406    pub fn from_fixed_term(scalar: &AssignedBoundedScalar<S::F>, base_name: &str) -> Self {
407        Self {
408            scalars: vec![],
409            bases: vec![],
410            fixed_base_scalars: [(base_name.to_string(), scalar.clone())].into_iter().collect(),
411        }
412    }
413
414    /// Adds a `(scalar, base)` term to the AssignedMsm.
415    pub fn add_term(&mut self, scalar: &AssignedBoundedScalar<S::F>, base: &S::AssignedPoint) {
416        self.scalars.push(scalar.clone());
417        self.bases.push(base.clone());
418    }
419
420    /// Adds two AssignedMsm.
421    pub fn add_msm(
422        &mut self,
423        layouter: &mut impl Layouter<S::F>,
424        scalar_chip: &S::ScalarChip,
425        other: &Self,
426    ) -> Result<(), Error> {
427        self.scalars.extend(other.scalars.clone());
428        self.bases.extend(other.bases.clone());
429
430        for (key, value) in other.fixed_base_scalars.clone() {
431            match self.fixed_base_scalars.entry(key) {
432                Entry::Occupied(mut occ) => {
433                    *occ.get_mut() = add_bounded_scalars(layouter, scalar_chip, occ.get(), &value)?;
434                }
435                Entry::Vacant(vac) => {
436                    vac.insert(value);
437                }
438            }
439        }
440
441        Ok(())
442    }
443
444    /// Evaluates the variable part of the AssignedMsm (the scalar-base pairs)
445    /// collapsing it to a single point (and a scalar of 1), leaving the
446    /// fixed-base part intact.
447    ///
448    /// This function mutates self.
449    pub fn collapse(
450        &mut self,
451        layouter: &mut impl Layouter<S::F>,
452        curve_chip: &S::CurveChip,
453        scalar_chip: &S::ScalarChip,
454    ) -> Result<(), Error> {
455        let scalars = self
456            .scalars
457            .iter()
458            .map(|s| (s.scalar.clone(), s.bound.bits() as usize))
459            .collect::<Vec<_>>();
460
461        let collapsed_base = S::msm(layouter, curve_chip, &scalars, &self.bases)?;
462
463        self.bases = vec![collapsed_base];
464        self.scalars = vec![AssignedBoundedScalar::one(layouter, scalar_chip)?];
465
466        Ok(())
467    }
468
469    /// Given the actual fixed bases, resolves the fixed-base part of the MSM
470    /// by pairing each named scalar with its base and moving them to regular
471    /// variable-base entries.
472    ///
473    /// After this call, `fixed_base_scalars` becomes empty.
474    ///
475    /// # Panics
476    ///
477    /// If some of the keys in `fixed_base_scalars` do not appear in the
478    /// provided `fixed_bases` map.
479    pub fn resolve_fixed_bases(&mut self, fixed_bases: &BTreeMap<String, S::AssignedPoint>) {
480        for (name, scalar) in &self.fixed_base_scalars {
481            let base = fixed_bases
482                .get(name)
483                .unwrap_or_else(|| panic!("Fixed base not provided: {name}"));
484            self.bases.push(base.clone());
485            self.scalars.push(scalar.clone());
486        }
487        self.fixed_base_scalars.clear();
488    }
489
490    /// Scales all the scalars of the AssignedMsm by the given factor r.
491    ///
492    /// This function mutates self.
493    pub fn scale(
494        &mut self,
495        layouter: &mut impl Layouter<S::F>,
496        scalar_chip: &S::ScalarChip,
497        r: &AssignedBoundedScalar<S::F>,
498    ) -> Result<(), Error> {
499        self.scalars = (self.scalars.iter())
500            .map(|s| mul_bounded_scalars(layouter, scalar_chip, s, r))
501            .collect::<Result<Vec<_>, Error>>()?;
502
503        for s in self.fixed_base_scalars.values_mut() {
504            *s = mul_bounded_scalars(layouter, scalar_chip, s, r)?;
505        }
506
507        Ok(())
508    }
509
510    /// Accumulates two AssignedMSMs with the a given scalar r.
511    /// The resulting AssignedMSMs evaluates (on `fixed_bases`) to
512    /// `self.eval(fixed_bases) + r * other.eval(fixed_bases)`.
513    pub fn accumulate_with_r(
514        &self,
515        layouter: &mut impl Layouter<S::F>,
516        scalar_chip: &S::ScalarChip,
517        other: &Self,
518        r: &AssignedBoundedScalar<S::F>,
519    ) -> Result<Self, Error> {
520        let mut other = other.clone();
521        other.scale(layouter, scalar_chip, r)?;
522
523        let mut acc = self.clone();
524        acc.add_msm(layouter, scalar_chip, &other)?;
525
526        Ok(acc)
527    }
528}