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

Commit 73893d3

Browse files
committed
Constrain token index
1 parent f263574 commit 73893d3

File tree

5 files changed

+434
-16
lines changed

5 files changed

+434
-16
lines changed

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

Lines changed: 209 additions & 13 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,245 @@ 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+
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 index_gate = IndexGate::create_gate(system, advice_pool.get_array());
8487
Self {
88+
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 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, 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+
index_value: Value<F>,
114+
) -> Result<(), Error> {
115+
let 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+
|| index_value,
123+
)
124+
},
125+
)?;
126+
127+
self.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+
index_cell.clone()
135+
}
136+
}),
106137
},
107138
)?;
108139

109140
self.public_inputs
110-
.constrain_cells(layouter, [(cell, TokenIndexInstance::TokenIndex)])?;
141+
.constrain_cells(layouter, [(index_cell, TokenIndexInstance::TokenIndex)])?;
111142
todo.check_off(Constraints::from(
112143
TokenIndexConstraints::TokenIndexInstanceIsConstrainedToAdvice,
113144
))
114145
}
115146
}
147+
148+
mod gates {
149+
use core::array;
150+
151+
use halo2_proofs::arithmetic::Field;
152+
153+
use crate::{
154+
consts::NUM_TOKENS,
155+
gates::linear_equation::{
156+
LinearEquationGate, LinearEquationGateConfig, LinearEquationGateInput,
157+
},
158+
AssignedCell, F,
159+
};
160+
161+
pub const NUM_INDEX_GATE_COLUMNS: usize = NUM_TOKENS + 1;
162+
163+
/// `0 * indicators[0] + 1 * indicators[1] + 2 * indicators[2] + ... = index`.
164+
pub type IndexGate = LinearEquationGate<NUM_INDEX_GATE_COLUMNS, IndexGateConfig>;
165+
pub type IndexGateInput = LinearEquationGateInput<AssignedCell, NUM_INDEX_GATE_COLUMNS>;
166+
167+
#[derive(Clone, Debug)]
168+
pub enum IndexGateConfig {}
169+
170+
impl LinearEquationGateConfig<NUM_INDEX_GATE_COLUMNS> for IndexGateConfig {
171+
fn coefficients() -> [F; NUM_INDEX_GATE_COLUMNS] {
172+
array::from_fn(|i| {
173+
if i < NUM_TOKENS {
174+
F::from(i as u64)
175+
} else {
176+
F::ONE.neg()
177+
}
178+
})
179+
}
180+
181+
fn constant_term() -> F {
182+
F::ZERO
183+
}
184+
185+
fn gate_name() -> &'static str {
186+
"Token index gate"
187+
}
188+
}
189+
}
190+
191+
#[cfg(test)]
192+
mod tests {
193+
use halo2_proofs::{
194+
circuit::{floor_planner, Layouter, Value},
195+
dev::{
196+
metadata::{Constraint, Gate},
197+
VerifyFailure,
198+
},
199+
plonk::{Advice, Circuit, ConstraintSystem, Error},
200+
};
201+
202+
use super::{gates, TokenIndexChip, TokenIndexInstance};
203+
use crate::{
204+
circuits::test_utils::expect_prover_success_and_run_verification, column_pool::ColumnPool,
205+
consts::NUM_TOKENS, deposit::DepositConstraints, embed::Embed,
206+
instance_wrapper::InstanceWrapper, test_utils::expect_instance_permutation_failures,
207+
todo::Todo, F,
208+
};
209+
210+
#[derive(Clone, Debug, Default)]
211+
struct TestCircuit {
212+
pub indicators: [Value<F>; NUM_TOKENS],
213+
214+
pub token_index: Value<F>,
215+
}
216+
217+
impl TestCircuit {
218+
pub fn new(indicators: [impl Into<F>; NUM_TOKENS], token_index: impl Into<F>) -> Self {
219+
Self {
220+
indicators: indicators.map(|v| Value::known(v.into())),
221+
token_index: Value::known(token_index.into()),
222+
}
223+
}
224+
}
225+
226+
impl Circuit<F> for TestCircuit {
227+
type Config = TokenIndexChip;
228+
type FloorPlanner = floor_planner::V1;
229+
230+
fn without_witnesses(&self) -> Self {
231+
Self::default()
232+
}
233+
234+
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
235+
let mut advice_pool = ColumnPool::<Advice>::new();
236+
advice_pool.ensure_capacity(meta, gates::NUM_INDEX_GATE_COLUMNS);
237+
let public_inputs = InstanceWrapper::<TokenIndexInstance>::new(meta);
238+
239+
TokenIndexChip::new(meta, advice_pool, public_inputs)
240+
}
241+
242+
fn synthesize(
243+
&self,
244+
chip: Self::Config,
245+
mut layouter: impl Layouter<F>,
246+
) -> Result<(), Error> {
247+
let indicators =
248+
self.indicators
249+
.embed(&mut layouter, &chip.advice_pool, "indicators")?;
250+
let mut todo = Todo::<DepositConstraints>::new();
251+
252+
chip.constrain_index_with_intermediates(
253+
&mut layouter,
254+
&indicators,
255+
&mut todo,
256+
self.token_index,
257+
)
258+
}
259+
}
260+
261+
#[test]
262+
fn native_token_passes() {
263+
let circuit = TestCircuit::new([1, 0, 0, 0, 0, 0], 0);
264+
let pub_input = [0];
265+
266+
assert!(
267+
expect_prover_success_and_run_verification(circuit, &pub_input.map(F::from)).is_ok()
268+
);
269+
}
270+
271+
#[test]
272+
fn nonnative_token_passes() {
273+
let circuit = TestCircuit::new([0, 1, 0, 0, 0, 0], 1);
274+
let pub_input = [1];
275+
276+
assert!(
277+
expect_prover_success_and_run_verification(circuit, &pub_input.map(F::from)).is_ok()
278+
);
279+
}
280+
281+
#[test]
282+
fn index_witness_is_constrained() {
283+
let circuit = TestCircuit::new([1, 0, 0, 0, 0, 0], 1);
284+
let pub_input = [1];
285+
286+
let failures = expect_prover_success_and_run_verification(circuit, &pub_input.map(F::from))
287+
.expect_err("Verification must fail");
288+
289+
assert_eq!(1, failures.len());
290+
match &failures[0] {
291+
VerifyFailure::ConstraintNotSatisfied { constraint, .. } => {
292+
assert_eq!(
293+
&Constraint::from((Gate::from((0, "Token index gate")), 0, "")),
294+
constraint
295+
);
296+
}
297+
_ => panic!("Unexpected error"),
298+
}
299+
}
300+
301+
#[test]
302+
fn index_pub_input_is_constrained() {
303+
let circuit = TestCircuit::new([1, 0, 0, 0, 0, 0], 0);
304+
let pub_input = [1];
305+
306+
let failures = expect_prover_success_and_run_verification(circuit, &pub_input.map(F::from))
307+
.expect_err("Verification must fail");
308+
309+
expect_instance_permutation_failures(&failures, "Token index", 0);
310+
}
311+
}

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)