Skip to content

Commit 4b0ea0b

Browse files
Add conditional swap chip
1 parent 4f87815 commit 4b0ea0b

File tree

2 files changed

+357
-0
lines changed

2 files changed

+357
-0
lines changed

src/circuit/gadget/utilities.rs

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use halo2::{
44
};
55
use pasta_curves::arithmetic::FieldExt;
66

7+
mod cond_swap;
78
mod plonk;
89

910
/// A variable representing a number.
+356
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
use super::{copy, CellValue, UtilitiesInstructions};
2+
use halo2::{
3+
circuit::{Cell, Chip, Layouter},
4+
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Permutation, Selector},
5+
poly::Rotation,
6+
};
7+
use pasta_curves::arithmetic::FieldExt;
8+
use std::marker::PhantomData;
9+
10+
pub trait CondSwapInstructions<F: FieldExt>: UtilitiesInstructions<F> {
11+
/// Variable representing cell with a certain value in the circuit.
12+
type Var;
13+
14+
/// Variable representing a `swap` boolean flag.
15+
type Swap: From<<Self as CondSwapInstructions<F>>::Var>;
16+
17+
/// Given an input pair (x,y) and a `swap` boolean flag, return
18+
/// (y,x) if `swap` is set, else (x,y) if `swap` is not set.
19+
fn swap(
20+
&self,
21+
layouter: impl Layouter<F>,
22+
pair: (
23+
<Self as CondSwapInstructions<F>>::Var,
24+
<Self as CondSwapInstructions<F>>::Var,
25+
),
26+
swap: Self::Swap,
27+
) -> Result<
28+
(
29+
<Self as CondSwapInstructions<F>>::Var,
30+
<Self as CondSwapInstructions<F>>::Var,
31+
),
32+
Error,
33+
>;
34+
}
35+
36+
/// A chip implementing a conditional swap.
37+
#[derive(Clone, Debug)]
38+
pub struct CondSwapChip<F> {
39+
config: CondSwapConfig,
40+
_marker: PhantomData<F>,
41+
}
42+
43+
impl<F: FieldExt> Chip<F> for CondSwapChip<F> {
44+
type Config = CondSwapConfig;
45+
type Loaded = ();
46+
47+
fn config(&self) -> &Self::Config {
48+
&self.config
49+
}
50+
51+
fn loaded(&self) -> &Self::Loaded {
52+
&()
53+
}
54+
}
55+
56+
#[derive(Clone, Debug)]
57+
pub struct CondSwapConfig {
58+
q_swap: Selector,
59+
x: Column<Advice>,
60+
y: Column<Advice>,
61+
x_swapped: Column<Advice>,
62+
y_swapped: Column<Advice>,
63+
swap: Column<Advice>,
64+
perm: Permutation,
65+
}
66+
67+
/// A variable representing a `swap` boolean flag.
68+
#[derive(Copy, Clone)]
69+
pub struct Swap {
70+
cell: Cell,
71+
value: Option<bool>,
72+
}
73+
74+
impl<F: FieldExt> From<CellValue<F>> for Swap {
75+
fn from(var: CellValue<F>) -> Self {
76+
let value = var.value.map(|value| {
77+
let zero = value == F::zero();
78+
let one = value == F::one();
79+
if zero {
80+
false
81+
} else if one {
82+
true
83+
} else {
84+
panic!("Value must be boolean.")
85+
}
86+
});
87+
Swap {
88+
cell: var.cell,
89+
value,
90+
}
91+
}
92+
}
93+
94+
impl<F: FieldExt> UtilitiesInstructions<F> for CondSwapChip<F> {
95+
type Var = CellValue<F>;
96+
}
97+
98+
impl<F: FieldExt> CondSwapInstructions<F> for CondSwapChip<F> {
99+
type Var = CellValue<F>;
100+
type Swap = Swap;
101+
102+
fn swap(
103+
&self,
104+
mut layouter: impl Layouter<F>,
105+
pair: (
106+
<Self as CondSwapInstructions<F>>::Var,
107+
<Self as CondSwapInstructions<F>>::Var,
108+
),
109+
swap: Self::Swap,
110+
) -> Result<
111+
(
112+
<Self as CondSwapInstructions<F>>::Var,
113+
<Self as CondSwapInstructions<F>>::Var,
114+
),
115+
Error,
116+
> {
117+
let config = self.config();
118+
119+
layouter.assign_region(
120+
|| "swap",
121+
|mut region| {
122+
// Enable `q_swap` selector
123+
config.q_swap.enable(&mut region, 0)?;
124+
125+
// Copy in `x` value
126+
let x = copy(&mut region, || "copy x", config.x, 0, &pair.0, &config.perm)?;
127+
128+
// Copy in `y` value
129+
let y = copy(&mut region, || "copy y", config.y, 0, &pair.1, &config.perm)?;
130+
131+
// Copy in `swap` value
132+
let swap_val = swap.value;
133+
let swap_cell = region.assign_advice(
134+
|| "swap",
135+
config.swap,
136+
0,
137+
|| {
138+
swap_val
139+
.map(|swap| F::from_u64(swap as u64))
140+
.ok_or(Error::SynthesisError)
141+
},
142+
)?;
143+
region.constrain_equal(&config.perm, swap_cell, swap.cell)?;
144+
145+
// Conditionally swap x
146+
let x_swapped = {
147+
let x_swapped =
148+
x.value
149+
.zip(y.value)
150+
.zip(swap_val)
151+
.map(|((x, y), swap)| if swap { y } else { x });
152+
let x_swapped_cell = region.assign_advice(
153+
|| "x_swapped",
154+
config.x_swapped,
155+
0,
156+
|| x_swapped.ok_or(Error::SynthesisError),
157+
)?;
158+
CellValue {
159+
cell: x_swapped_cell,
160+
value: x_swapped,
161+
}
162+
};
163+
164+
// Conditionally swap y
165+
let y_swapped = {
166+
let y_swapped =
167+
x.value
168+
.zip(y.value)
169+
.zip(swap_val)
170+
.map(|((x, y), swap)| if swap { x } else { y });
171+
let y_swapped_cell = region.assign_advice(
172+
|| "y_swapped",
173+
config.y_swapped,
174+
0,
175+
|| y_swapped.ok_or(Error::SynthesisError),
176+
)?;
177+
CellValue {
178+
cell: y_swapped_cell,
179+
value: y_swapped,
180+
}
181+
};
182+
183+
// Return swapped pair
184+
Ok((x_swapped, y_swapped))
185+
},
186+
)
187+
}
188+
}
189+
190+
impl<F: FieldExt> CondSwapChip<F> {
191+
/// Configures this chip for use in a circuit.
192+
pub fn configure(
193+
meta: &mut ConstraintSystem<F>,
194+
advices: [Column<Advice>; 5],
195+
perm: Permutation,
196+
) -> CondSwapConfig {
197+
let q_swap = meta.selector();
198+
199+
let config = CondSwapConfig {
200+
q_swap,
201+
x: advices[0],
202+
y: advices[1],
203+
x_swapped: advices[2],
204+
y_swapped: advices[3],
205+
swap: advices[4],
206+
perm,
207+
};
208+
209+
// TODO: optimise shape of gate for Merkle path validation
210+
211+
meta.create_gate("x' = y ⋅ swap + x ⋅ (1-swap)", |meta| {
212+
let q_swap = meta.query_selector(q_swap, Rotation::cur());
213+
214+
let x = meta.query_advice(config.x, Rotation::cur());
215+
let y = meta.query_advice(config.y, Rotation::cur());
216+
let x_swapped = meta.query_advice(config.x_swapped, Rotation::cur());
217+
let y_swapped = meta.query_advice(config.y_swapped, Rotation::cur());
218+
let swap = meta.query_advice(config.swap, Rotation::cur());
219+
220+
let one = Expression::Constant(F::one());
221+
222+
// x_swapped - y ⋅ swap - x ⋅ (1-swap) = 0
223+
// This checks that `x_swapped` is equal to `y` when `swap` is set,
224+
// but remains as `x` when `swap` is not set.
225+
let x_check =
226+
x_swapped - y.clone() * swap.clone() - x.clone() * (one.clone() - swap.clone());
227+
228+
// y_swapped - x ⋅ swap - y ⋅ (1-swap) = 0
229+
// This checks that `y_swapped` is equal to `x` when `swap` is set,
230+
// but remains as `y` when `swap` is not set.
231+
let y_check = y_swapped - x * swap.clone() - y * (one.clone() - swap.clone());
232+
233+
// Check `swap` is boolean.
234+
let bool_check = swap.clone() * (one - swap);
235+
236+
[x_check, y_check, bool_check]
237+
.iter()
238+
.map(|poly| q_swap.clone() * poly.clone())
239+
.collect()
240+
});
241+
242+
config
243+
}
244+
245+
pub fn construct(config: CondSwapConfig) -> Self {
246+
CondSwapChip {
247+
config,
248+
_marker: PhantomData,
249+
}
250+
}
251+
}
252+
253+
#[cfg(test)]
254+
mod tests {
255+
use super::super::UtilitiesInstructions;
256+
use super::{CondSwapChip, CondSwapConfig, CondSwapInstructions};
257+
use halo2::{
258+
circuit::{layouter::SingleChipLayouter, Layouter},
259+
dev::MockProver,
260+
plonk::{Any, Assignment, Circuit, Column, ConstraintSystem, Error},
261+
};
262+
use pasta_curves::{arithmetic::FieldExt, pallas::Base};
263+
264+
#[test]
265+
fn cond_swap() {
266+
struct MyCircuit<F: FieldExt> {
267+
x: Option<F>,
268+
y: Option<F>,
269+
swap: Option<F>,
270+
}
271+
272+
impl<F: FieldExt> Circuit<F> for MyCircuit<F> {
273+
type Config = CondSwapConfig;
274+
275+
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
276+
let advices = [
277+
meta.advice_column(),
278+
meta.advice_column(),
279+
meta.advice_column(),
280+
meta.advice_column(),
281+
meta.advice_column(),
282+
];
283+
284+
let perm = meta.permutation(
285+
&advices
286+
.iter()
287+
.map(|advice| (*advice).into())
288+
.collect::<Vec<Column<Any>>>(),
289+
);
290+
291+
CondSwapChip::<F>::configure(meta, advices, perm)
292+
}
293+
294+
fn synthesize(
295+
&self,
296+
cs: &mut impl Assignment<F>,
297+
config: Self::Config,
298+
) -> Result<(), Error> {
299+
let mut layouter = SingleChipLayouter::new(cs)?;
300+
let chip = CondSwapChip::<F>::construct(config.clone());
301+
302+
// Load the pair and the swap flag into the circuit.
303+
let x = chip.load_private(layouter.namespace(|| "x"), config.x, self.x)?;
304+
let y = chip.load_private(layouter.namespace(|| "y"), config.y, self.y)?;
305+
let swap =
306+
chip.load_private(layouter.namespace(|| "swap"), config.swap, self.swap)?;
307+
308+
// Return the swapped pair.
309+
let swapped_pair =
310+
chip.swap(layouter.namespace(|| "swap"), (x, y).into(), swap.into())?;
311+
312+
if let Some(swap) = self.swap {
313+
if swap == F::one() {
314+
// Check that `x` and `y` have been swapped
315+
assert_eq!(swapped_pair.0.value.unwrap(), y.value.unwrap());
316+
assert_eq!(swapped_pair.1.value.unwrap(), x.value.unwrap());
317+
} else {
318+
// Check that `x` and `y` have not been swapped
319+
assert_eq!(swapped_pair.0.value.unwrap(), x.value.unwrap());
320+
assert_eq!(swapped_pair.1.value.unwrap(), y.value.unwrap());
321+
}
322+
}
323+
324+
Ok(())
325+
}
326+
}
327+
328+
// Test swap case
329+
{
330+
let circuit: MyCircuit<Base> = MyCircuit {
331+
x: Some(Base::rand()),
332+
y: Some(Base::rand()),
333+
swap: Some(Base::one()),
334+
};
335+
let prover = match MockProver::<Base>::run(3, &circuit, vec![]) {
336+
Ok(prover) => prover,
337+
Err(e) => panic!("{:?}", e),
338+
};
339+
assert_eq!(prover.verify(), Ok(()));
340+
}
341+
342+
// Test non-swap case
343+
{
344+
let circuit: MyCircuit<Base> = MyCircuit {
345+
x: Some(Base::rand()),
346+
y: Some(Base::rand()),
347+
swap: Some(Base::zero()),
348+
};
349+
let prover = match MockProver::<Base>::run(3, &circuit, vec![]) {
350+
Ok(prover) => prover,
351+
Err(e) => panic!("{:?}", e),
352+
};
353+
assert_eq!(prover.verify(), Ok(()));
354+
}
355+
}
356+
}

0 commit comments

Comments
 (0)