|
1 | 1 | use core::array;
|
2 | 2 |
|
| 3 | +use gates::{IndexGate, IndexGateInput}; |
3 | 4 | use halo2_proofs::{
|
4 |
| - circuit::Layouter, |
5 |
| - plonk::{Advice, Error}, |
| 5 | + circuit::{Layouter, Value}, |
| 6 | + plonk::{Advice, ConstraintSystem, Error}, |
6 | 7 | };
|
7 | 8 | use strum::IntoEnumIterator;
|
8 | 9 | use strum_macros::{EnumCount, EnumIter};
|
9 | 10 |
|
10 | 11 | use crate::{
|
11 |
| - column_pool::ColumnPool, consts::NUM_TOKENS, instance_wrapper::InstanceWrapper, todo::Todo, |
12 |
| - AssignedCell, F, |
| 12 | + column_pool::ColumnPool, consts::NUM_TOKENS, gates::Gate, instance_wrapper::InstanceWrapper, |
| 13 | + todo::Todo, AssignedCell, F, |
13 | 14 | };
|
14 | 15 |
|
15 | 16 | pub mod off_circuit {
|
@@ -66,50 +67,247 @@ pub enum TokenIndexConstraints {
|
66 | 67 | TokenIndexInstanceIsConstrainedToAdvice,
|
67 | 68 | }
|
68 | 69 |
|
69 |
| -// TODO: Replace the hacky underconstrained index production with a gate application. |
70 | 70 | // TODO: Constrain indicators to the set {0,1}.
|
71 | 71 | // TODO: Constrain that exactly one indicator has value 1.
|
72 | 72 | // A chip that manages the token index indicator variables and related constraints.
|
73 | 73 | #[derive(Clone, Debug)]
|
74 | 74 | pub struct TokenIndexChip {
|
| 75 | + token_index_gate: IndexGate, |
75 | 76 | advice_pool: ColumnPool<Advice>,
|
76 | 77 | public_inputs: InstanceWrapper<TokenIndexInstance>,
|
77 | 78 | }
|
78 | 79 |
|
79 | 80 | impl TokenIndexChip {
|
80 | 81 | pub fn new(
|
| 82 | + system: &mut ConstraintSystem<F>, |
81 | 83 | advice_pool: ColumnPool<Advice>,
|
82 | 84 | public_inputs: InstanceWrapper<TokenIndexInstance>,
|
83 | 85 | ) -> Self {
|
| 86 | + let token_index_gate = IndexGate::create_gate(system, advice_pool.get_array()); |
84 | 87 | Self {
|
| 88 | + token_index_gate, |
85 | 89 | advice_pool,
|
86 | 90 | public_inputs,
|
87 | 91 | }
|
88 | 92 | }
|
89 | 93 |
|
90 |
| - /// Temporary hack: the function should apply a gate to produce the index from indicators, |
91 |
| - /// by the formula `index = 0 * indicators[0] + 1 * indicators[1] + 2 * indicators[2] + ...`, |
92 |
| - /// but for now it just produces a cell with an unconstrained value. |
93 | 94 | pub fn constrain_index<Constraints: From<TokenIndexConstraints> + Ord + IntoEnumIterator>(
|
94 | 95 | &self,
|
95 | 96 | layouter: &mut impl Layouter<F>,
|
96 | 97 | indicators: &[AssignedCell; NUM_TOKENS],
|
97 | 98 | todo: &mut Todo<Constraints>,
|
98 | 99 | ) -> Result<(), Error> {
|
99 |
| - let values = array::from_fn(|i| indicators[i].value().cloned()); |
100 |
| - let index = off_circuit::index_from_indicator_values(&values); |
| 100 | + let indicator_values = array::from_fn(|i| indicators[i].value().cloned()); |
| 101 | + let token_index_value = off_circuit::index_from_indicator_values(&indicator_values); |
101 | 102 |
|
102 |
| - let cell = layouter.assign_region( |
| 103 | + self.constrain_index_with_intermediates(layouter, indicators, todo, token_index_value) |
| 104 | + } |
| 105 | + |
| 106 | + fn constrain_index_with_intermediates< |
| 107 | + Constraints: From<TokenIndexConstraints> + Ord + IntoEnumIterator, |
| 108 | + >( |
| 109 | + &self, |
| 110 | + layouter: &mut impl Layouter<F>, |
| 111 | + indicators: &[AssignedCell; NUM_TOKENS], |
| 112 | + todo: &mut Todo<Constraints>, |
| 113 | + token_index_value: Value<F>, |
| 114 | + ) -> Result<(), Error> { |
| 115 | + let token_index_cell = layouter.assign_region( |
103 | 116 | || "Token index",
|
104 | 117 | |mut region| {
|
105 |
| - region.assign_advice(|| "Token index", self.advice_pool.get_any(), 0, || index) |
| 118 | + region.assign_advice( |
| 119 | + || "Token index", |
| 120 | + self.advice_pool.get_any(), |
| 121 | + 0, |
| 122 | + || token_index_value, |
| 123 | + ) |
106 | 124 | },
|
107 | 125 | )?;
|
108 | 126 |
|
109 |
| - self.public_inputs |
110 |
| - .constrain_cells(layouter, [(cell, TokenIndexInstance::TokenIndex)])?; |
| 127 | + self.token_index_gate.apply_in_new_region( |
| 128 | + layouter, |
| 129 | + IndexGateInput { |
| 130 | + variables: array::from_fn(|i| { |
| 131 | + if i < NUM_TOKENS { |
| 132 | + indicators[i].clone() |
| 133 | + } else { |
| 134 | + token_index_cell.clone() |
| 135 | + } |
| 136 | + }), |
| 137 | + }, |
| 138 | + )?; |
| 139 | + |
| 140 | + self.public_inputs.constrain_cells( |
| 141 | + layouter, |
| 142 | + [(token_index_cell, TokenIndexInstance::TokenIndex)], |
| 143 | + )?; |
111 | 144 | todo.check_off(Constraints::from(
|
112 | 145 | TokenIndexConstraints::TokenIndexInstanceIsConstrainedToAdvice,
|
113 | 146 | ))
|
114 | 147 | }
|
115 | 148 | }
|
| 149 | + |
| 150 | +mod gates { |
| 151 | + use core::array; |
| 152 | + |
| 153 | + use halo2_proofs::arithmetic::Field; |
| 154 | + |
| 155 | + use crate::{ |
| 156 | + consts::NUM_TOKENS, |
| 157 | + gates::linear_equation::{ |
| 158 | + LinearEquationGate, LinearEquationGateConfig, LinearEquationGateInput, |
| 159 | + }, |
| 160 | + AssignedCell, F, |
| 161 | + }; |
| 162 | + |
| 163 | + pub const NUM_INDEX_GATE_COLUMNS: usize = NUM_TOKENS + 1; |
| 164 | + |
| 165 | + /// `0 * indicators[0] + 1 * indicators[1] + 2 * indicators[2] + ... = index`. |
| 166 | + pub type IndexGate = LinearEquationGate<NUM_INDEX_GATE_COLUMNS, IndexGateConfig>; |
| 167 | + pub type IndexGateInput = LinearEquationGateInput<AssignedCell, NUM_INDEX_GATE_COLUMNS>; |
| 168 | + |
| 169 | + #[derive(Clone, Debug)] |
| 170 | + pub enum IndexGateConfig {} |
| 171 | + |
| 172 | + impl LinearEquationGateConfig<NUM_INDEX_GATE_COLUMNS> for IndexGateConfig { |
| 173 | + fn coefficients() -> [F; NUM_INDEX_GATE_COLUMNS] { |
| 174 | + array::from_fn(|i| { |
| 175 | + if i < NUM_TOKENS { |
| 176 | + F::from(i as u64) |
| 177 | + } else { |
| 178 | + F::ONE.neg() |
| 179 | + } |
| 180 | + }) |
| 181 | + } |
| 182 | + |
| 183 | + fn constant_term() -> F { |
| 184 | + F::ZERO |
| 185 | + } |
| 186 | + |
| 187 | + fn gate_name() -> &'static str { |
| 188 | + "Token index gate" |
| 189 | + } |
| 190 | + } |
| 191 | +} |
| 192 | + |
| 193 | +#[cfg(test)] |
| 194 | +mod tests { |
| 195 | + use halo2_proofs::{ |
| 196 | + circuit::{floor_planner, Layouter, Value}, |
| 197 | + dev::{ |
| 198 | + metadata::{Constraint, Gate}, |
| 199 | + VerifyFailure, |
| 200 | + }, |
| 201 | + plonk::{Advice, Circuit, ConstraintSystem, Error}, |
| 202 | + }; |
| 203 | + |
| 204 | + use super::{gates, TokenIndexChip, TokenIndexInstance}; |
| 205 | + use crate::{ |
| 206 | + circuits::test_utils::expect_prover_success_and_run_verification, column_pool::ColumnPool, |
| 207 | + consts::NUM_TOKENS, deposit::DepositConstraints, embed::Embed, |
| 208 | + instance_wrapper::InstanceWrapper, test_utils::expect_instance_permutation_failures, |
| 209 | + todo::Todo, F, |
| 210 | + }; |
| 211 | + |
| 212 | + #[derive(Clone, Debug, Default)] |
| 213 | + struct TestCircuit { |
| 214 | + pub indicators: [Value<F>; NUM_TOKENS], |
| 215 | + |
| 216 | + pub token_index: Value<F>, |
| 217 | + } |
| 218 | + |
| 219 | + impl TestCircuit { |
| 220 | + pub fn new(indicators: [impl Into<F>; NUM_TOKENS], token_index: impl Into<F>) -> Self { |
| 221 | + Self { |
| 222 | + indicators: indicators.map(|v| Value::known(v.into())), |
| 223 | + token_index: Value::known(token_index.into()), |
| 224 | + } |
| 225 | + } |
| 226 | + } |
| 227 | + |
| 228 | + impl Circuit<F> for TestCircuit { |
| 229 | + type Config = TokenIndexChip; |
| 230 | + type FloorPlanner = floor_planner::V1; |
| 231 | + |
| 232 | + fn without_witnesses(&self) -> Self { |
| 233 | + Self::default() |
| 234 | + } |
| 235 | + |
| 236 | + fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config { |
| 237 | + let mut advice_pool = ColumnPool::<Advice>::new(); |
| 238 | + advice_pool.ensure_capacity(meta, gates::NUM_INDEX_GATE_COLUMNS); |
| 239 | + let public_inputs = InstanceWrapper::<TokenIndexInstance>::new(meta); |
| 240 | + |
| 241 | + TokenIndexChip::new(meta, advice_pool, public_inputs) |
| 242 | + } |
| 243 | + |
| 244 | + fn synthesize( |
| 245 | + &self, |
| 246 | + chip: Self::Config, |
| 247 | + mut layouter: impl Layouter<F>, |
| 248 | + ) -> Result<(), Error> { |
| 249 | + let indicators = |
| 250 | + self.indicators |
| 251 | + .embed(&mut layouter, &chip.advice_pool, "indicators")?; |
| 252 | + let mut todo = Todo::<DepositConstraints>::new(); |
| 253 | + |
| 254 | + chip.constrain_index_with_intermediates( |
| 255 | + &mut layouter, |
| 256 | + &indicators, |
| 257 | + &mut todo, |
| 258 | + self.token_index, |
| 259 | + ) |
| 260 | + } |
| 261 | + } |
| 262 | + |
| 263 | + #[test] |
| 264 | + fn native_token_passes() { |
| 265 | + let circuit = TestCircuit::new([1, 0, 0, 0, 0, 0], 0); |
| 266 | + let pub_input = [0]; |
| 267 | + |
| 268 | + assert!( |
| 269 | + expect_prover_success_and_run_verification(circuit, &pub_input.map(F::from)).is_ok() |
| 270 | + ); |
| 271 | + } |
| 272 | + |
| 273 | + #[test] |
| 274 | + fn nonnative_token_passes() { |
| 275 | + let circuit = TestCircuit::new([0, 1, 0, 0, 0, 0], 1); |
| 276 | + let pub_input = [1]; |
| 277 | + |
| 278 | + assert!( |
| 279 | + expect_prover_success_and_run_verification(circuit, &pub_input.map(F::from)).is_ok() |
| 280 | + ); |
| 281 | + } |
| 282 | + |
| 283 | + #[test] |
| 284 | + fn index_witness_is_constrained() { |
| 285 | + let circuit = TestCircuit::new([1, 0, 0, 0, 0, 0], 1); |
| 286 | + let pub_input = [1]; |
| 287 | + |
| 288 | + let failures = expect_prover_success_and_run_verification(circuit, &pub_input.map(F::from)) |
| 289 | + .expect_err("Verification must fail"); |
| 290 | + |
| 291 | + assert_eq!(1, failures.len()); |
| 292 | + match &failures[0] { |
| 293 | + VerifyFailure::ConstraintNotSatisfied { constraint, .. } => { |
| 294 | + assert_eq!( |
| 295 | + &Constraint::from((Gate::from((0, "Token index gate")), 0, "")), |
| 296 | + constraint |
| 297 | + ); |
| 298 | + } |
| 299 | + _ => panic!("Unexpected error"), |
| 300 | + } |
| 301 | + } |
| 302 | + |
| 303 | + #[test] |
| 304 | + fn index_pub_input_is_constrained() { |
| 305 | + let circuit = TestCircuit::new([1, 0, 0, 0, 0, 0], 0); |
| 306 | + let pub_input = [1]; |
| 307 | + |
| 308 | + let failures = expect_prover_success_and_run_verification(circuit, &pub_input.map(F::from)) |
| 309 | + .expect_err("Verification must fail"); |
| 310 | + |
| 311 | + expect_instance_permutation_failures(&failures, "Token index", 0); |
| 312 | + } |
| 313 | +} |
0 commit comments