|
|
@@ -15,6 +15,7 @@ pub struct RangeCheckConfig {
|
|
|
pub k_values_table: TableColumn,
|
|
|
}
|
|
|
|
|
|
+#[derive(Clone, Debug)]
|
|
|
pub struct RangeCheckChip<F: FieldExt + PrimeFieldBits, const WINDOW_SIZE: usize> {
|
|
|
config: RangeCheckConfig,
|
|
|
_marker: PhantomData<F>,
|
|
|
@@ -36,10 +37,14 @@ impl<F: FieldExt + PrimeFieldBits, const WINDOW_SIZE: usize> Chip<F>
|
|
|
}
|
|
|
|
|
|
impl<F: FieldExt + PrimeFieldBits, const WINDOW_SIZE: usize> RangeCheckChip<F, WINDOW_SIZE> {
|
|
|
+ pub fn construct(config: RangeCheckConfig) -> Self {
|
|
|
+ Self { config, _marker: PhantomData }
|
|
|
+ }
|
|
|
+
|
|
|
pub fn configure(
|
|
|
meta: &mut ConstraintSystem<F>,
|
|
|
k_values_table: TableColumn,
|
|
|
- ) -> <Self as Chip<F>>::Config {
|
|
|
+ ) -> RangeCheckConfig {
|
|
|
let z = meta.advice_column();
|
|
|
let s_rc = meta.complex_selector();
|
|
|
|
|
|
@@ -80,60 +85,68 @@ impl<F: FieldExt + PrimeFieldBits, const WINDOW_SIZE: usize> RangeCheckChip<F, W
|
|
|
)
|
|
|
}
|
|
|
|
|
|
- pub fn range_check(
|
|
|
+ pub fn witness_range_check(
|
|
|
&self,
|
|
|
- mut layouter: impl Layouter<F>,
|
|
|
- element: AssignedCell<F, F>,
|
|
|
+ layouter: &mut impl Layouter<F>,
|
|
|
+ value: Value<F>,
|
|
|
+ offset: usize,
|
|
|
num_of_bits: usize,
|
|
|
num_of_windows: usize,
|
|
|
) -> Result<(), Error> {
|
|
|
- assert!(WINDOW_SIZE * num_of_windows < num_of_bits + WINDOW_SIZE);
|
|
|
-
|
|
|
layouter.assign_region(
|
|
|
|| "name",
|
|
|
|mut region: Region<'_, F>| {
|
|
|
- // enable selectors
|
|
|
- for index in 0..num_of_windows {
|
|
|
- self.config.s_rc.enable(&mut region, index);
|
|
|
- }
|
|
|
-
|
|
|
- // copy element to z_0
|
|
|
- let z_0 = element.copy_advice(|| "z_0", &mut region, self.config.z, 0)?;
|
|
|
-
|
|
|
- let mut z_values: Vec<AssignedCell<F, F>> = vec![z_0.clone()];
|
|
|
- let mut z = z_0.clone();
|
|
|
- let decomposed_chunks = z_0
|
|
|
- .value()
|
|
|
- .map(|val| decompose_value::<F, WINDOW_SIZE>(val, num_of_bits))
|
|
|
- .transpose_vec(num_of_windows);
|
|
|
-
|
|
|
- let two_pow_k_inverse =
|
|
|
- Value::known(F::from(1 << WINDOW_SIZE as u64).invert().unwrap());
|
|
|
- for (i, chunk) in decomposed_chunks.iter().enumerate() {
|
|
|
- let z_next = {
|
|
|
- let z_curr = z.value().copied();
|
|
|
- let chunk_value = chunk
|
|
|
- .map(|c| F::from(c.iter().fold(0, |acc, c| (acc << 1) + *c as u64)));
|
|
|
- // z_next = (z_curr - k_i) / 2^K
|
|
|
- let z_next = (z_curr - chunk_value) * two_pow_k_inverse;
|
|
|
- region.assign_advice(
|
|
|
- || format!("z_{}", i + 1),
|
|
|
- self.config.z,
|
|
|
- i + 1,
|
|
|
- || z_next,
|
|
|
- )?
|
|
|
- };
|
|
|
- z_values.push(z_next.clone());
|
|
|
- z = z_next.clone();
|
|
|
- }
|
|
|
-
|
|
|
- assert!(z_values.len() == num_of_windows + 1);
|
|
|
-
|
|
|
- region.constrain_constant(z_values.last().unwrap().cell(), F::zero())?;
|
|
|
-
|
|
|
+ let z_0 = region.assign_advice(|| "z_0", self.config.z, offset, || value)?;
|
|
|
+ self.decompose(region, z_0, offset, num_of_bits, num_of_windows)?;
|
|
|
Ok(())
|
|
|
},
|
|
|
- );
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn decompose(
|
|
|
+ &self,
|
|
|
+ mut region: Region<'_, F>,
|
|
|
+ z_0: AssignedCell<F, F>,
|
|
|
+ offset: usize,
|
|
|
+ num_of_bits: usize,
|
|
|
+ num_of_windows: usize,
|
|
|
+ ) -> Result<(), Error> {
|
|
|
+ assert!(WINDOW_SIZE * num_of_windows < num_of_bits + WINDOW_SIZE);
|
|
|
+
|
|
|
+ // enable selectors
|
|
|
+ for index in 0..num_of_windows {
|
|
|
+ self.config.s_rc.enable(&mut region, index + offset)?;
|
|
|
+ }
|
|
|
+
|
|
|
+ let mut z_values: Vec<AssignedCell<F, F>> = vec![z_0.clone()];
|
|
|
+ let mut z = z_0.clone();
|
|
|
+ let decomposed_chunks = z_0
|
|
|
+ .value()
|
|
|
+ .map(|val| decompose_value::<F, WINDOW_SIZE>(val, num_of_bits))
|
|
|
+ .transpose_vec(num_of_windows);
|
|
|
+
|
|
|
+ let two_pow_k_inverse = Value::known(F::from(1 << WINDOW_SIZE as u64).invert().unwrap());
|
|
|
+ for (i, chunk) in decomposed_chunks.iter().enumerate() {
|
|
|
+ let z_next = {
|
|
|
+ let z_curr = z.value().copied();
|
|
|
+ let chunk_value =
|
|
|
+ chunk.map(|c| F::from(c.iter().fold(0, |acc, c| (acc << 1) + *c as u64)));
|
|
|
+ // z_next = (z_curr - k_i) / 2^K
|
|
|
+ let z_next = (z_curr - chunk_value) * two_pow_k_inverse;
|
|
|
+ region.assign_advice(
|
|
|
+ || format!("z_{}", i + offset + 1),
|
|
|
+ self.config.z,
|
|
|
+ i + offset,
|
|
|
+ || z_next,
|
|
|
+ )?
|
|
|
+ };
|
|
|
+ z_values.push(z_next.clone());
|
|
|
+ z = z_next.clone();
|
|
|
+ }
|
|
|
+
|
|
|
+ assert!(z_values.len() == num_of_windows + 1);
|
|
|
+
|
|
|
+ region.constrain_constant(z_values.last().unwrap().cell(), F::zero())?;
|
|
|
|
|
|
Ok(())
|
|
|
}
|
|
|
@@ -162,3 +175,68 @@ pub fn decompose_value<F: FieldExt + PrimeFieldBits, const WINDOW_SIZE: usize>(
|
|
|
})
|
|
|
.collect()
|
|
|
}
|
|
|
+
|
|
|
+#[cfg(test)]
|
|
|
+mod tests {
|
|
|
+ use super::*;
|
|
|
+ use group::ff::PrimeFieldBits;
|
|
|
+ use halo2_proofs::{
|
|
|
+ arithmetic::FieldExt,
|
|
|
+ circuit::{SimpleFloorPlanner, Value},
|
|
|
+ dev::MockProver,
|
|
|
+ plonk::Circuit,
|
|
|
+ };
|
|
|
+ use pasta_curves::pallas;
|
|
|
+
|
|
|
+ struct MyCircuit<
|
|
|
+ F: FieldExt + PrimeFieldBits,
|
|
|
+ const WINDOW_SIZE: usize,
|
|
|
+ const NUM_OF_BITS: usize,
|
|
|
+ const NUM_OF_WINDOWS: usize,
|
|
|
+ > {
|
|
|
+ value: Value<F>,
|
|
|
+ }
|
|
|
+
|
|
|
+ impl<
|
|
|
+ F: FieldExt + PrimeFieldBits,
|
|
|
+ const WINDOW_SIZE: usize,
|
|
|
+ const NUM_OF_BITS: usize,
|
|
|
+ const NUM_OF_WINDOWS: usize,
|
|
|
+ > Circuit<F> for MyCircuit<F, WINDOW_SIZE, NUM_OF_BITS, NUM_OF_WINDOWS>
|
|
|
+ {
|
|
|
+ type Config = RangeCheckConfig;
|
|
|
+ type FloorPlanner = SimpleFloorPlanner;
|
|
|
+
|
|
|
+ fn without_witnesses(&self) -> Self {
|
|
|
+ Self { value: Value::unknown() }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
|
|
|
+ let table_column = meta.lookup_table_column();
|
|
|
+ RangeCheckChip::<F, WINDOW_SIZE>::configure(meta, table_column)
|
|
|
+ }
|
|
|
+
|
|
|
+ fn synthesize(
|
|
|
+ &self,
|
|
|
+ config: Self::Config,
|
|
|
+ layouter: impl Layouter<F>,
|
|
|
+ ) -> Result<(), Error> {
|
|
|
+ let chip = RangeCheckChip::<F, WINDOW_SIZE>::construct(config.clone());
|
|
|
+
|
|
|
+ // construct `WINDOW_SIZE` lookup table
|
|
|
+ RangeCheckChip::<F, WINDOW_SIZE>::load_k_table(&mut layouter, config.k_values_table);
|
|
|
+
|
|
|
+ chip.witness_range_check(&mut layouter, self.value, 0, NUM_OF_BITS, NUM_OF_WINDOWS);
|
|
|
+
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn test_bit() {
|
|
|
+ let value = pallas::Base::from(rand::random::<u64>());
|
|
|
+ let circuit = MyCircuit::<pallas::Base, 3, 64, 22> { value: Value::known(value) };
|
|
|
+ let prover = MockProver::run(8, &circuit, vec![]).unwrap();
|
|
|
+ assert_ne!(prover.verify(), Ok(()));
|
|
|
+ }
|
|
|
+}
|