1use core::marker::PhantomData;
23
24use halo2_proofs::{
25 circuit::{AssignedCell, Chip, Layouter, Region},
26 pasta::group::ff::WithSmallOrderMulGroup,
27 plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector},
28 poly::Rotation,
29};
30
31pub const NUM_OF_UTILITY_ADVICE_COLUMNS: usize = 4;
32
33#[derive(Clone, Debug)]
34pub struct ConditionalSelectConfig<F: WithSmallOrderMulGroup<3> + Ord> {
35 advices: [Column<Advice>; NUM_OF_UTILITY_ADVICE_COLUMNS],
36 s_cs: Selector,
37 _marker: PhantomData<F>,
38}
39
40pub struct ConditionalSelectChip<F: WithSmallOrderMulGroup<3> + Ord> {
41 config: ConditionalSelectConfig<F>,
42}
43
44impl<F: WithSmallOrderMulGroup<3> + Ord> Chip<F> for ConditionalSelectChip<F> {
45 type Config = ConditionalSelectConfig<F>;
46 type Loaded = ();
47
48 fn config(&self) -> &Self::Config {
49 &self.config
50 }
51
52 fn loaded(&self) -> &Self::Loaded {
53 &()
54 }
55}
56
57impl<F: WithSmallOrderMulGroup<3> + Ord> ConditionalSelectChip<F> {
58 pub fn construct(config: <Self as Chip<F>>::Config) -> Self {
59 Self { config }
60 }
61
62 pub fn configure(
63 meta: &mut ConstraintSystem<F>,
64 advices: [Column<Advice>; NUM_OF_UTILITY_ADVICE_COLUMNS],
65 ) -> <Self as Chip<F>>::Config {
66 for column in &advices {
67 meta.enable_equality(*column);
68 }
69 let s_cs = meta.selector();
70
71 meta.create_gate("conditional_select", |meta| {
72 let lhs = meta.query_advice(advices[0], Rotation::cur());
73 let rhs = meta.query_advice(advices[1], Rotation::cur());
74 let out = meta.query_advice(advices[2], Rotation::cur());
75 let cond = meta.query_advice(advices[3], Rotation::cur());
76 let s_cs = meta.query_selector(s_cs);
77 let one = Expression::Constant(F::ONE);
78
79 vec![
80 s_cs.clone() * (cond.clone() * (one - cond.clone())),
82 s_cs * ((lhs - rhs.clone()) * cond + rhs - out),
84 ]
85 });
86
87 ConditionalSelectConfig { advices, s_cs, _marker: PhantomData }
88 }
89
90 pub fn conditional_select(
91 &self,
92 layouter: &mut impl Layouter<F>,
93 a: AssignedCell<F, F>,
94 b: AssignedCell<F, F>,
95 cond: AssignedCell<F, F>,
96 ) -> Result<AssignedCell<F, F>, Error> {
97 let config = self.config();
98 let out = layouter.assign_region(
99 || "conditional_select",
100 |mut region: Region<'_, F>| {
101 config.s_cs.enable(&mut region, 0)?;
102
103 a.copy_advice(|| "copy a", &mut region, config.advices[0], 0)?;
104 b.copy_advice(|| "copy b", &mut region, config.advices[1], 0)?;
105
106 let cond = cond.copy_advice(|| "copy cond", &mut region, config.advices[3], 0)?;
107
108 let selected = cond
109 .value()
110 .copied()
111 .to_field()
112 .zip(a.value())
113 .zip(b.value())
114 .map(|((cond, a), b)| if cond == F::ONE.into() { a } else { b })
115 .copied();
116
117 let cell =
118 region.assign_advice(|| "select result", config.advices[2], 0, || selected)?;
119
120 Ok(cell)
121 },
122 )?;
123 Ok(out)
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130 use crate::zk::assign_free_advice;
131
132 use halo2_proofs::{
133 arithmetic::Field,
134 circuit::{floor_planner, Value},
135 dev::MockProver,
136 pasta::pallas,
137 plonk,
138 plonk::{Circuit, Instance},
139 };
140 use rand::rngs::OsRng;
141
142 #[derive(Clone)]
143 struct CondSelectConfig {
144 primary: Column<Instance>,
145 condselect_config: ConditionalSelectConfig<pallas::Base>,
146 }
147
148 #[derive(Default)]
149 struct CondSelectCircuit {
150 pub cond: Value<pallas::Base>,
151 pub a: Value<pallas::Base>,
152 pub b: Value<pallas::Base>,
153 }
154
155 impl Circuit<pallas::Base> for CondSelectCircuit {
156 type Config = CondSelectConfig;
157 type FloorPlanner = floor_planner::V1;
158 type Params = ();
159
160 fn without_witnesses(&self) -> Self {
161 Self::default()
162 }
163
164 fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> Self::Config {
165 let advices: [Column<Advice>; NUM_OF_UTILITY_ADVICE_COLUMNS] = [
166 meta.advice_column(),
167 meta.advice_column(),
168 meta.advice_column(),
169 meta.advice_column(),
170 ];
171
172 let primary = meta.instance_column();
173 meta.enable_equality(primary);
174
175 for advice in advices.iter() {
176 meta.enable_equality(*advice);
177 }
178
179 let condselect_config = ConditionalSelectChip::configure(meta, advices);
180
181 Self::Config { primary, condselect_config }
182 }
183
184 fn synthesize(
185 &self,
186 config: Self::Config,
187 mut layouter: impl Layouter<pallas::Base>,
188 ) -> Result<(), plonk::Error> {
189 let condselect_chip =
190 ConditionalSelectChip::construct(config.condselect_config.clone());
191
192 let cond = assign_free_advice(
193 layouter.namespace(|| "Witness cond"),
194 config.condselect_config.advices[0],
195 self.cond,
196 )?;
197
198 let a = assign_free_advice(
199 layouter.namespace(|| "Witness a"),
200 config.condselect_config.advices[1],
201 self.a,
202 )?;
203
204 let b = assign_free_advice(
205 layouter.namespace(|| "Witness b"),
206 config.condselect_config.advices[2],
207 self.b,
208 )?;
209
210 let selection = condselect_chip.conditional_select(&mut layouter, a, b, cond)?;
211 layouter.constrain_instance(selection.cell(), config.primary, 0)?;
212
213 Ok(())
214 }
215 }
216
217 #[test]
218 fn cond_select_chip() -> crate::Result<()> {
219 let cond = pallas::Base::ONE;
221 let a = pallas::Base::random(&mut OsRng);
222 let b = pallas::Base::random(&mut OsRng);
223 let public_inputs = vec![a];
224
225 let circuit =
226 CondSelectCircuit { cond: Value::known(cond), a: Value::known(a), b: Value::known(b) };
227
228 let prover = MockProver::run(4, &circuit, vec![public_inputs])?;
229 prover.assert_satisfied();
230
231 let cond = pallas::Base::ZERO;
233 let a = pallas::Base::random(&mut OsRng);
234 let b = pallas::Base::random(&mut OsRng);
235 let public_inputs = vec![b];
236
237 let circuit =
238 CondSelectCircuit { cond: Value::known(cond), a: Value::known(a), b: Value::known(b) };
239
240 let prover = MockProver::run(4, &circuit, vec![public_inputs])?;
241 prover.assert_satisfied();
242
243 Ok(())
244 }
245}