acir/native_types/expression/
mod.rs

1use crate::native_types::Witness;
2use acir_field::FieldElement;
3use serde::{Deserialize, Serialize};
4use std::cmp::Ordering;
5
6mod operators;
7mod ordering;
8
9// In the addition polynomial
10// We can have arbitrary fan-in/out, so we need more than wL,wR and wO
11// When looking at the assert-zero opcode for the quotient polynomial in standard plonk
12// You can think of it as fan-in 2 and fan out-1 , or you can think of it as fan-in 1 and fan-out 2
13//
14// In the multiplication polynomial
15// XXX: If we allow the degree of the quotient polynomial to be arbitrary, then we will need a vector of wire values
16#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)]
17pub struct Expression {
18    // To avoid having to create intermediate variables pre-optimization
19    // We collect all of the multiplication terms in the assert-zero opcode
20    // A multiplication term if of the form q_M * wL * wR
21    // Hence this vector represents the following sum: q_M1 * wL1 * wR1 + q_M2 * wL2 * wR2 + .. +
22    pub mul_terms: Vec<(FieldElement, Witness, Witness)>,
23
24    pub linear_combinations: Vec<(FieldElement, Witness)>,
25    // TODO: rename q_c to `constant` moreover q_X is not clear to those who
26    // TODO are not familiar with PLONK
27    pub q_c: FieldElement,
28}
29
30impl Default for Expression {
31    fn default() -> Expression {
32        Expression {
33            mul_terms: Vec::new(),
34            linear_combinations: Vec::new(),
35            q_c: FieldElement::zero(),
36        }
37    }
38}
39
40impl std::fmt::Display for Expression {
41    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
42        if let Some(witness) = self.to_witness() {
43            write!(f, "x{}", witness.witness_index())
44        } else {
45            write!(f, "%{:?}%", crate::circuit::opcodes::Opcode::AssertZero(self.clone()))
46        }
47    }
48}
49
50impl Expression {
51    // TODO: possibly remove, and move to noir repo.
52    pub const fn can_defer_constraint(&self) -> bool {
53        false
54    }
55
56    /// Returns the number of multiplication terms
57    pub fn num_mul_terms(&self) -> usize {
58        self.mul_terms.len()
59    }
60
61    pub fn from_field(q_c: FieldElement) -> Expression {
62        Self { q_c, ..Default::default() }
63    }
64
65    pub fn one() -> Expression {
66        Self::from_field(FieldElement::one())
67    }
68
69    pub fn zero() -> Expression {
70        Self::default()
71    }
72
73    /// Adds a new linear term to the `Expression`.
74    pub fn push_addition_term(&mut self, coefficient: FieldElement, variable: Witness) {
75        self.linear_combinations.push((coefficient, variable));
76    }
77
78    /// Adds a new quadratic term to the `Expression`.
79    pub fn push_multiplication_term(
80        &mut self,
81        coefficient: FieldElement,
82        lhs: Witness,
83        rhs: Witness,
84    ) {
85        self.mul_terms.push((coefficient, lhs, rhs));
86    }
87
88    /// Returns `true` if the expression represents a constant polynomial.
89    ///
90    /// Examples:
91    /// -  f(x,y) = x + y would return false
92    /// -  f(x,y) = xy would return false, the degree here is 2
93    /// -  f(x,y) = 5 would return true, the degree is 0
94    pub fn is_const(&self) -> bool {
95        self.mul_terms.is_empty() && self.linear_combinations.is_empty()
96    }
97
98    /// Returns `true` if highest degree term in the expression is one or less.
99    ///
100    /// - `mul_term` in an expression contains degree-2 terms
101    /// - `linear_combinations` contains degree-1 terms
102    /// Hence, it is sufficient to check that there are no `mul_terms`
103    ///
104    /// Examples:
105    /// -  f(x,y) = x + y would return true
106    /// -  f(x,y) = xy would return false, the degree here is 2
107    /// -  f(x,y) = 0 would return true, the degree is 0
108    pub fn is_linear(&self) -> bool {
109        self.mul_terms.is_empty()
110    }
111
112    /// Returns `true` if the expression can be seen as a degree-1 univariate polynomial
113    ///
114    /// - `mul_terms` in an expression can be univariate, however unless the coefficient
115    /// is zero, it is always degree-2.
116    /// - `linear_combinations` contains the sum of degree-1 terms, these terms do not
117    /// need to contain the same variable and so it can be multivariate. However, we
118    /// have thus far only checked if `linear_combinations` contains one term, so this
119    /// method will return false, if the `Expression` has not been simplified.
120    ///
121    /// Hence, we check in the simplest case if an expression is a degree-1 univariate,
122    /// by checking if it contains no `mul_terms` and it contains one `linear_combination` term.
123    ///
124    /// Examples:
125    /// - f(x,y) = x would return true
126    /// - f(x,y) = x + 6 would return true
127    /// - f(x,y) = 2*y + 6 would return true
128    /// - f(x,y) = x + y would return false
129    /// - f(x,y) = x + x should return true, but we return false *** (we do not simplify)
130    /// - f(x,y) = 5 would return false
131    pub fn is_degree_one_univariate(&self) -> bool {
132        self.is_linear() && self.linear_combinations.len() == 1
133    }
134
135    pub fn is_zero(&self) -> bool {
136        *self == Self::zero()
137    }
138
139    /// Returns a `FieldElement` if the expression represents a constant polynomial.
140    /// Otherwise returns `None`.
141    ///
142    /// Examples:
143    /// - f(x,y) = x would return `None`
144    /// - f(x,y) = x + 6 would return `None`
145    /// - f(x,y) = 2*y + 6 would return `None`
146    /// - f(x,y) = x + y would return `None`
147    /// - f(x,y) = 5 would return `FieldElement(5)`
148    pub fn to_const(&self) -> Option<FieldElement> {
149        self.is_const().then_some(self.q_c)
150    }
151
152    /// Returns a `Witness` if the `Expression` can be represented as a degree-1
153    /// univariate polynomial. Otherwise returns `None`.
154    ///
155    /// Note that `Witness` is only capable of expressing polynomials of the form
156    /// f(x) = x and not polynomials of the form f(x) = mx+c , so this method has
157    /// extra checks to ensure that m=1 and c=0
158    pub fn to_witness(&self) -> Option<Witness> {
159        if self.is_degree_one_univariate() {
160            // If we get here, we know that our expression is of the form `f(x) = mx+c`
161            // We want to now restrict ourselves to expressions of the form f(x) = x
162            // ie where the constant term is 0 and the coefficient in front of the variable is
163            // one.
164            let (coefficient, variable) = self.linear_combinations[0];
165            let constant = self.q_c;
166
167            if coefficient.is_one() && constant.is_zero() {
168                return Some(variable);
169            }
170        }
171        None
172    }
173
174    /// Sorts opcode in a deterministic order
175    /// XXX: We can probably make this more efficient by sorting on each phase. We only care if it is deterministic
176    pub fn sort(&mut self) {
177        self.mul_terms.sort_by(|a, b| a.1.cmp(&b.1).then(a.2.cmp(&b.2)));
178        self.linear_combinations.sort_by(|a, b| a.1.cmp(&b.1));
179    }
180
181    /// Checks if this expression can fit into one arithmetic identity
182    /// TODO: This needs to be reworded, arithmetic identity only makes sense in the context
183    /// TODO of PLONK, whereas we want expressions to be generic.
184    /// TODO: We just need to reword it to say exactly what its doing and
185    /// TODO then reference the fact that this is what plonk will accept.
186    /// TODO alternatively, we can define arithmetic identity in the context of expressions
187    /// TODO and then reference that.
188    pub fn fits_in_one_identity(&self, width: usize) -> bool {
189        // A Polynomial with more than one mul term cannot fit into one opcode
190        if self.mul_terms.len() > 1 {
191            return false;
192        };
193        // A Polynomial with more terms than fan-in cannot fit within a single opcode
194        if self.linear_combinations.len() > width {
195            return false;
196        }
197
198        // A polynomial with no mul term and a fan-in that fits inside of the width can fit into a single opcode
199        if self.mul_terms.is_empty() {
200            return true;
201        }
202
203        // A polynomial with width-2 fan-in terms and a single non-zero mul term can fit into one opcode
204        // Example: Axy + Dz . Notice, that the mul term places a constraint on the first two terms, but not the last term
205        // XXX: This would change if our arithmetic polynomial equation was changed to Axyz for example, but for now it is not.
206        if self.linear_combinations.len() <= (width - 2) {
207            return true;
208        }
209
210        // We now know that we have a single mul term. We also know that the mul term must match up with two other terms
211        // A polynomial whose mul terms are non zero which do not match up with two terms in the fan-in cannot fit into one opcode
212        // An example of this is: Axy + Bx + Cy + ...
213        // Notice how the bivariate monomial xy has two univariate monomials with their respective coefficients
214        // XXX: note that if x or y is zero, then we could apply a further optimization, but this would be done in another algorithm.
215        // It would be the same as when we have zero coefficients - Can only work if wire is constrained to be zero publicly
216        let mul_term = &self.mul_terms[0];
217
218        // The coefficient should be non-zero, as this method is ran after the compiler removes all zero coefficient terms
219        assert_ne!(mul_term.0, FieldElement::zero());
220
221        let mut found_x = false;
222        let mut found_y = false;
223
224        for term in self.linear_combinations.iter() {
225            let witness = &term.1;
226            let x = &mul_term.1;
227            let y = &mul_term.2;
228            if witness == x {
229                found_x = true;
230            };
231            if witness == y {
232                found_y = true;
233            };
234            if found_x & found_y {
235                break;
236            }
237        }
238
239        found_x & found_y
240    }
241
242    /// Returns `self + k*b`
243    pub fn add_mul(&self, k: FieldElement, b: &Expression) -> Expression {
244        if k.is_zero() {
245            return self.clone();
246        } else if self.is_const() {
247            return self.q_c + (k * b);
248        } else if b.is_const() {
249            return self.clone() + (k * b.q_c);
250        }
251
252        let mut mul_terms: Vec<(FieldElement, Witness, Witness)> =
253            Vec::with_capacity(self.mul_terms.len() + b.mul_terms.len());
254        let mut linear_combinations: Vec<(FieldElement, Witness)> =
255            Vec::with_capacity(self.linear_combinations.len() + b.linear_combinations.len());
256        let q_c = self.q_c + k * b.q_c;
257
258        //linear combinations
259        let mut i1 = 0; //a
260        let mut i2 = 0; //b
261        while i1 < self.linear_combinations.len() && i2 < b.linear_combinations.len() {
262            let (a_c, a_w) = self.linear_combinations[i1];
263            let (b_c, b_w) = b.linear_combinations[i2];
264
265            let (coeff, witness) = match a_w.cmp(&b_w) {
266                Ordering::Greater => {
267                    i2 += 1;
268                    (k * b_c, b_w)
269                }
270                Ordering::Less => {
271                    i1 += 1;
272                    (a_c, a_w)
273                }
274                Ordering::Equal => {
275                    // Here we're taking both witnesses as the witness indices are equal.
276                    // We then advance both `i1` and `i2`.
277                    i1 += 1;
278                    i2 += 1;
279                    (a_c + k * b_c, a_w)
280                }
281            };
282
283            if !coeff.is_zero() {
284                linear_combinations.push((coeff, witness));
285            }
286        }
287
288        // Finally process all the remaining terms which we didn't handle in the above loop.
289        while i1 < self.linear_combinations.len() {
290            linear_combinations.push(self.linear_combinations[i1]);
291            i1 += 1;
292        }
293        while i2 < b.linear_combinations.len() {
294            let (b_c, b_w) = b.linear_combinations[i2];
295            let coeff = b_c * k;
296            if !coeff.is_zero() {
297                linear_combinations.push((coeff, b_w));
298            }
299            i2 += 1;
300        }
301
302        //mul terms
303
304        i1 = 0; //a
305        i2 = 0; //b
306        while i1 < self.mul_terms.len() && i2 < b.mul_terms.len() {
307            let (a_c, a_wl, a_wr) = self.mul_terms[i1];
308            let (b_c, b_wl, b_wr) = b.mul_terms[i2];
309
310            let (coeff, wl, wr) = match (a_wl, a_wr).cmp(&(b_wl, b_wr)) {
311                Ordering::Greater => {
312                    i2 += 1;
313                    (k * b_c, b_wl, b_wr)
314                }
315                Ordering::Less => {
316                    i1 += 1;
317                    (a_c, a_wl, a_wr)
318                }
319                Ordering::Equal => {
320                    // Here we're taking both terms as the witness indices are equal.
321                    // We then advance both `i1` and `i2`.
322                    i2 += 1;
323                    i1 += 1;
324                    (a_c + k * b_c, a_wl, a_wr)
325                }
326            };
327
328            if !coeff.is_zero() {
329                mul_terms.push((coeff, wl, wr));
330            }
331        }
332
333        // Finally process all the remaining terms which we didn't handle in the above loop.
334        while i1 < self.mul_terms.len() {
335            mul_terms.push(self.mul_terms[i1]);
336            i1 += 1;
337        }
338        while i2 < b.mul_terms.len() {
339            let (b_c, b_wl, b_wr) = b.mul_terms[i2];
340            let coeff = b_c * k;
341            if coeff != FieldElement::zero() {
342                mul_terms.push((coeff, b_wl, b_wr));
343            }
344            i2 += 1;
345        }
346
347        Expression { mul_terms, linear_combinations, q_c }
348    }
349}
350
351impl From<FieldElement> for Expression {
352    fn from(constant: FieldElement) -> Expression {
353        Expression { q_c: constant, linear_combinations: Vec::new(), mul_terms: Vec::new() }
354    }
355}
356
357impl From<Witness> for Expression {
358    /// Creates an Expression from a Witness.
359    ///
360    /// This is infallible since an `Expression` is
361    /// a multi-variate polynomial and a `Witness`
362    /// can be seen as a univariate polynomial
363    fn from(wit: Witness) -> Expression {
364        Expression {
365            q_c: FieldElement::zero(),
366            linear_combinations: vec![(FieldElement::one(), wit)],
367            mul_terms: Vec::new(),
368        }
369    }
370}
371
372#[test]
373fn add_mul_smoketest() {
374    let a = Expression {
375        mul_terms: vec![(FieldElement::from(2u128), Witness(1), Witness(2))],
376        ..Default::default()
377    };
378
379    let k = FieldElement::from(10u128);
380
381    let b = Expression {
382        mul_terms: vec![
383            (FieldElement::from(3u128), Witness(0), Witness(2)),
384            (FieldElement::from(3u128), Witness(1), Witness(2)),
385            (FieldElement::from(4u128), Witness(4), Witness(5)),
386        ],
387        linear_combinations: vec![(FieldElement::from(4u128), Witness(4))],
388        q_c: FieldElement::one(),
389    };
390
391    let result = a.add_mul(k, &b);
392    assert_eq!(
393        result,
394        Expression {
395            mul_terms: vec![
396                (FieldElement::from(30u128), Witness(0), Witness(2)),
397                (FieldElement::from(32u128), Witness(1), Witness(2)),
398                (FieldElement::from(40u128), Witness(4), Witness(5)),
399            ],
400            linear_combinations: vec![(FieldElement::from(40u128), Witness(4))],
401            q_c: FieldElement::from(10u128)
402        }
403    );
404}