Skip to content
This repository was archived by the owner on Aug 5, 2025. It is now read-only.

Commit a1e8acc

Browse files
committed
Constrain token index
1 parent f263574 commit a1e8acc

File tree

6 files changed

+438
-17
lines changed

6 files changed

+438
-17
lines changed

crates/shielder-circuits/src/chips/token_index.rs

Lines changed: 212 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
use core::array;
22

3+
use gates::{IndexGate, IndexGateInput};
34
use halo2_proofs::{
4-
circuit::Layouter,
5-
plonk::{Advice, Error},
5+
circuit::{Layouter, Value},
6+
plonk::{Advice, ConstraintSystem, Error},
67
};
78
use strum::IntoEnumIterator;
89
use strum_macros::{EnumCount, EnumIter};
910

1011
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,
1314
};
1415

1516
pub mod off_circuit {
@@ -66,50 +67,247 @@ pub enum TokenIndexConstraints {
6667
TokenIndexInstanceIsConstrainedToAdvice,
6768
}
6869

69-
// TODO: Replace the hacky underconstrained index production with a gate application.
7070
// TODO: Constrain indicators to the set {0,1}.
7171
// TODO: Constrain that exactly one indicator has value 1.
7272
// A chip that manages the token index indicator variables and related constraints.
7373
#[derive(Clone, Debug)]
7474
pub struct TokenIndexChip {
75+
token_index_gate: IndexGate,
7576
advice_pool: ColumnPool<Advice>,
7677
public_inputs: InstanceWrapper<TokenIndexInstance>,
7778
}
7879

7980
impl TokenIndexChip {
8081
pub fn new(
82+
system: &mut ConstraintSystem<F>,
8183
advice_pool: ColumnPool<Advice>,
8284
public_inputs: InstanceWrapper<TokenIndexInstance>,
8385
) -> Self {
86+
let token_index_gate = IndexGate::create_gate(system, advice_pool.get_array());
8487
Self {
88+
token_index_gate,
8589
advice_pool,
8690
public_inputs,
8791
}
8892
}
8993

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.
9394
pub fn constrain_index<Constraints: From<TokenIndexConstraints> + Ord + IntoEnumIterator>(
9495
&self,
9596
layouter: &mut impl Layouter<F>,
9697
indicators: &[AssignedCell; NUM_TOKENS],
9798
todo: &mut Todo<Constraints>,
9899
) -> 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);
101102

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(
103116
|| "Token index",
104117
|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+
)
106124
},
107125
)?;
108126

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+
)?;
111144
todo.check_off(Constraints::from(
112145
TokenIndexConstraints::TokenIndexInstanceIsConstrainedToAdvice,
113146
))
114147
}
115148
}
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+
}

crates/shielder-circuits/src/circuits/deposit/circuit.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,16 @@ impl Circuit<F> for DepositCircuit {
3737

3838
let (advice_pool, poseidon, merkle) = configs_builder.resolve_merkle();
3939
let (_, balances_increase) = configs_builder.resolve_balances_increase_chip();
40-
let token_index = TokenIndexChip::new(advice_pool.clone(), public_inputs.narrow());
40+
let range_check = configs_builder.resolve_range_check();
41+
42+
let token_index = TokenIndexChip::new(meta, advice_pool.clone(), public_inputs.narrow());
4143

4244
DepositChip {
4345
advice_pool,
4446
public_inputs,
4547
poseidon,
4648
merkle,
47-
range_check: configs_builder.resolve_range_check(),
49+
range_check,
4850
balances_increase,
4951
token_index,
5052
}

crates/shielder-circuits/src/circuits/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub mod withdraw;
2626

2727
pub mod marshall;
2828
#[cfg(test)]
29-
mod test_utils;
29+
pub mod test_utils;
3030

3131
pub type AssignedCell = halo2_proofs::circuit::AssignedCell<F, F>;
3232

0 commit comments

Comments
 (0)