1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
use ark_ff::Field;
use kimchi::circuits::expr::{AlphaChallengeTerm, CacheId, ConstantExpr, Expr, FormattedOutput};
use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
fmt::{Display, Formatter, Result},
ops::Index,
};
use strum_macros::{EnumCount as EnumCountMacro, EnumIter};
use crate::NUMBER_OF_COLUMNS;
/// This enum represents the different gadgets that can be used in the circuit.
/// The selectors are defined at setup time, can take only the values `0` or
/// `1` and are public.
// IMPROVEME: should we merge it with the Instruction enum?
// It might not be that obvious to do so, as the Instruction enum could be
// defining operations that are not "fixed" in the circuit, but rather
// depend on runtime values (e.g. in a zero-knowledge virtual machine).
#[derive(Debug, Clone, Copy, PartialEq, EnumCountMacro, EnumIter)]
pub enum Gadget {
App,
// Elliptic curve related gadgets
EllipticCurveAddition,
EllipticCurveScaling,
/// This gadget implement the Poseidon hash instance described in the
/// top-level documentation. This implementation does use the "next row"
/// to allow the computation of one additional round per row. In the current
/// setup, with [crate::NUMBER_OF_COLUMNS] columns, we can compute 5 full
/// rounds per row.
Poseidon,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Column {
Selector(Gadget),
PublicInput(usize),
X(usize),
}
/// Convert a column to a usize. This is used by the library [mvpoly] when we
/// need to compute the cross-terms.
/// For now, only the private inputs and the public inputs are converted,
/// because there might not need to treat the selectors in the polynomial while
/// computing the cross-terms (FIXME: check this later, but pretty sure it's the
/// case).
///
/// Also, the [mvpoly::monomials] implementation of the trait [mvpoly::MVPoly]
/// will be used, and the mapping here is consistent with the one expected by
/// this implementation, i.e. we simply map to an increasing number starting at
/// 0, without any gap.
impl From<Column> for usize {
fn from(val: Column) -> usize {
match val {
Column::X(i) => i,
Column::PublicInput(i) => NUMBER_OF_COLUMNS + i,
Column::Selector(_) => unimplemented!("Selectors are not supported. This method is supposed to be called only to compute the cross-term and an optimisation is in progress to avoid the inclusion of the selectors in the multi-variate polynomial."),
}
}
}
pub struct Challenges<F: Field> {
/// Challenge used to aggregate the constraints
pub alpha: F,
/// Both challenges used in the permutation argument
pub beta: F,
pub gamma: F,
/// Challenge to homogenize the constraints
pub homogenous_challenge: F,
/// Random coin used to aggregate witnesses while folding
pub r: F,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ChallengeTerm {
/// Challenge used to aggregate the constraints
Alpha,
/// Both challenges used in the permutation argument
Beta,
Gamma,
/// Challenge to homogenize the constraints
HomogenousChallenge,
/// Random coin used to aggregate witnesses while folding
R,
}
impl Display for ChallengeTerm {
fn fmt(&self, f: &mut Formatter) -> Result {
match self {
ChallengeTerm::Alpha => write!(f, "alpha"),
ChallengeTerm::Beta => write!(f, "beta"),
ChallengeTerm::Gamma => write!(f, "gamma"),
ChallengeTerm::HomogenousChallenge => write!(f, "u"),
ChallengeTerm::R => write!(f, "r"),
}
}
}
impl<F: Field> Index<ChallengeTerm> for Challenges<F> {
type Output = F;
fn index(&self, term: ChallengeTerm) -> &Self::Output {
match term {
ChallengeTerm::Alpha => &self.alpha,
ChallengeTerm::Beta => &self.beta,
ChallengeTerm::Gamma => &self.gamma,
ChallengeTerm::HomogenousChallenge => &self.homogenous_challenge,
ChallengeTerm::R => &self.r,
}
}
}
impl<'a> AlphaChallengeTerm<'a> for ChallengeTerm {
const ALPHA: Self = Self::Alpha;
}
pub type E<Fp> = Expr<ConstantExpr<Fp, ChallengeTerm>, Column>;
// Code to allow for pretty printing of the expressions
impl FormattedOutput for Column {
fn latex(&self, _cache: &mut HashMap<CacheId, Self>) -> String {
match self {
Column::Selector(sel) => match sel {
Gadget::App => "q_app".to_string(),
Gadget::EllipticCurveAddition => "q_ec_add".to_string(),
Gadget::EllipticCurveScaling => "q_ec_mul".to_string(),
Gadget::Poseidon => "q_pos".to_string(),
},
Column::PublicInput(i) => format!("pi_{{{i}}}").to_string(),
Column::X(i) => format!("x_{{{i}}}").to_string(),
}
}
fn text(&self, _cache: &mut HashMap<CacheId, Self>) -> String {
match self {
Column::Selector(sel) => match sel {
Gadget::App => "q_app".to_string(),
Gadget::EllipticCurveAddition => "q_ec_add".to_string(),
Gadget::EllipticCurveScaling => "q_ec_mul".to_string(),
Gadget::Poseidon => "q_pos_next_row".to_string(),
},
Column::PublicInput(i) => format!("pi[{i}]"),
Column::X(i) => format!("x[{i}]"),
}
}
fn ocaml(&self, _cache: &mut HashMap<CacheId, Self>) -> String {
// FIXME
unimplemented!("Not used at the moment")
}
fn is_alpha(&self) -> bool {
// FIXME
unimplemented!("Not used at the moment")
}
}