2021-06-12 05:21:55 -07:00
|
|
|
//! Make use of a K-bit lookup table to decompose a field element into K-bit
|
|
|
|
//! words.
|
|
|
|
|
2022-01-27 15:28:02 -08:00
|
|
|
use halo2_proofs::{
|
2021-12-01 18:51:46 -08:00
|
|
|
circuit::{AssignedCell, Layouter, Region},
|
2022-04-24 15:13:38 -07:00
|
|
|
plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Selector, TableColumn},
|
2021-06-12 05:21:55 -07:00
|
|
|
poly::Rotation,
|
|
|
|
};
|
2021-06-13 08:20:13 -07:00
|
|
|
use std::{convert::TryInto, marker::PhantomData};
|
2021-06-12 05:21:55 -07:00
|
|
|
|
|
|
|
use ff::PrimeFieldBits;
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
2021-07-24 08:54:54 -07:00
|
|
|
/// The running sum $[z_0, ..., z_W]$. If created in strict mode, $z_W = 0$.
|
2022-01-27 13:53:10 -08:00
|
|
|
#[derive(Debug)]
|
2022-11-29 16:12:17 -08:00
|
|
|
pub struct RunningSum<F: PrimeFieldBits>(Vec<AssignedCell<F, F>>);
|
|
|
|
impl<F: PrimeFieldBits> std::ops::Deref for RunningSum<F> {
|
2021-12-01 18:51:46 -08:00
|
|
|
type Target = Vec<AssignedCell<F, F>>;
|
2021-07-24 08:54:54 -07:00
|
|
|
|
2021-12-01 18:51:46 -08:00
|
|
|
fn deref(&self) -> &Vec<AssignedCell<F, F>> {
|
2021-07-24 08:54:54 -07:00
|
|
|
&self.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-29 16:12:17 -08:00
|
|
|
impl<F: PrimeFieldBits> RangeConstrained<F, AssignedCell<F, F>> {
|
2022-05-02 09:01:07 -07:00
|
|
|
/// Witnesses a subset of the bits in `value` and constrains them to be the correct
|
|
|
|
/// number of bits.
|
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
///
|
|
|
|
/// Panics if `bitrange.len() >= K`.
|
|
|
|
pub fn witness_short<const K: usize>(
|
|
|
|
lookup_config: &LookupRangeCheckConfig<F, K>,
|
|
|
|
layouter: impl Layouter<F>,
|
2022-06-08 13:37:52 -07:00
|
|
|
value: Value<&F>,
|
2022-05-02 09:01:07 -07:00
|
|
|
bitrange: Range<usize>,
|
|
|
|
) -> Result<Self, Error> {
|
|
|
|
let num_bits = bitrange.len();
|
|
|
|
assert!(num_bits < K);
|
|
|
|
|
|
|
|
// Witness the subset and constrain it to be the correct number of bits.
|
|
|
|
lookup_config
|
|
|
|
.witness_short_check(
|
|
|
|
layouter,
|
|
|
|
value.map(|value| bitrange_subset(value, bitrange)),
|
|
|
|
num_bits,
|
|
|
|
)
|
|
|
|
.map(|inner| Self {
|
|
|
|
inner,
|
|
|
|
num_bits,
|
|
|
|
_phantom: PhantomData::default(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-28 07:38:22 -08:00
|
|
|
/// Configuration that provides methods for a lookup range check.
|
2021-11-30 11:31:42 -08:00
|
|
|
#[derive(Eq, PartialEq, Debug, Clone, Copy)]
|
2022-11-29 16:12:17 -08:00
|
|
|
pub struct LookupRangeCheckConfig<F: PrimeFieldBits, const K: usize> {
|
2022-01-28 07:38:22 -08:00
|
|
|
q_lookup: Selector,
|
|
|
|
q_running: Selector,
|
|
|
|
q_bitshift: Selector,
|
|
|
|
running_sum: Column<Advice>,
|
2021-07-27 10:32:32 -07:00
|
|
|
table_idx: TableColumn,
|
2021-06-12 18:40:50 -07:00
|
|
|
_marker: PhantomData<F>,
|
2021-06-12 05:21:55 -07:00
|
|
|
}
|
|
|
|
|
2022-11-29 16:12:17 -08:00
|
|
|
impl<F: PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K> {
|
2021-06-13 08:20:13 -07:00
|
|
|
/// The `running_sum` advice column breaks the field element into `K`-bit
|
|
|
|
/// words. It is used to construct the input expression to the lookup
|
|
|
|
/// argument.
|
|
|
|
///
|
|
|
|
/// The `table_idx` fixed column contains values from [0..2^K). Looking up
|
|
|
|
/// a value in `table_idx` constrains it to be within this range. The table
|
|
|
|
/// can be loaded outside this helper.
|
2021-07-15 04:52:15 -07:00
|
|
|
///
|
|
|
|
/// # Side-effects
|
|
|
|
///
|
|
|
|
/// Both the `running_sum` and `constants` columns will be equality-enabled.
|
2021-06-12 05:21:55 -07:00
|
|
|
pub fn configure(
|
|
|
|
meta: &mut ConstraintSystem<F>,
|
|
|
|
running_sum: Column<Advice>,
|
2021-07-27 10:32:32 -07:00
|
|
|
table_idx: TableColumn,
|
2021-06-12 18:40:50 -07:00
|
|
|
) -> Self {
|
2022-01-04 21:28:16 -08:00
|
|
|
meta.enable_equality(running_sum);
|
2021-07-15 04:52:15 -07:00
|
|
|
|
2021-07-26 05:54:27 -07:00
|
|
|
let q_lookup = meta.complex_selector();
|
2021-07-26 21:15:21 -07:00
|
|
|
let q_running = meta.complex_selector();
|
|
|
|
let q_bitshift = meta.selector();
|
2021-06-12 05:21:55 -07:00
|
|
|
let config = LookupRangeCheckConfig {
|
|
|
|
q_lookup,
|
2021-07-26 21:15:21 -07:00
|
|
|
q_running,
|
|
|
|
q_bitshift,
|
2021-06-12 05:21:55 -07:00
|
|
|
running_sum,
|
|
|
|
table_idx,
|
2021-06-12 18:40:50 -07:00
|
|
|
_marker: PhantomData,
|
2021-06-12 05:21:55 -07:00
|
|
|
};
|
|
|
|
|
2022-05-08 20:27:03 -07:00
|
|
|
// https://p.z.cash/halo2-0.1:decompose-combined-lookup
|
2021-06-12 05:21:55 -07:00
|
|
|
meta.lookup(|meta| {
|
2021-06-24 03:23:01 -07:00
|
|
|
let q_lookup = meta.query_selector(config.q_lookup);
|
2021-07-26 21:15:21 -07:00
|
|
|
let q_running = meta.query_selector(config.q_running);
|
2021-06-12 05:21:55 -07:00
|
|
|
let z_cur = meta.query_advice(config.running_sum, Rotation::cur());
|
2021-07-26 21:15:21 -07:00
|
|
|
|
|
|
|
// In the case of a running sum decomposition, we recover the word from
|
|
|
|
// the difference of the running sums:
|
2021-06-12 05:21:55 -07:00
|
|
|
// z_i = 2^{K}⋅z_{i + 1} + a_i
|
|
|
|
// => a_i = z_i - 2^{K}⋅z_{i + 1}
|
2021-07-26 21:15:21 -07:00
|
|
|
let running_sum_lookup = {
|
|
|
|
let running_sum_word = {
|
|
|
|
let z_next = meta.query_advice(config.running_sum, Rotation::next());
|
2021-12-07 09:47:03 -08:00
|
|
|
z_cur.clone() - z_next * F::from(1 << K)
|
2021-07-26 21:15:21 -07:00
|
|
|
};
|
2021-06-12 05:21:55 -07:00
|
|
|
|
2021-07-26 21:15:21 -07:00
|
|
|
q_running.clone() * running_sum_word
|
|
|
|
};
|
2021-06-12 05:21:55 -07:00
|
|
|
|
2021-07-26 21:15:21 -07:00
|
|
|
// In the short range check, the word is directly witnessed.
|
|
|
|
let short_lookup = {
|
|
|
|
let short_word = z_cur;
|
2022-11-29 21:05:37 -08:00
|
|
|
let q_short = Expression::Constant(F::ONE) - q_running;
|
2021-07-26 21:15:21 -07:00
|
|
|
|
|
|
|
q_short * short_word
|
|
|
|
};
|
2021-06-24 03:23:01 -07:00
|
|
|
|
2021-07-26 21:15:21 -07:00
|
|
|
// Combine the running sum and short lookups:
|
|
|
|
vec![(
|
|
|
|
q_lookup * (running_sum_lookup + short_lookup),
|
|
|
|
config.table_idx,
|
|
|
|
)]
|
2021-06-24 03:23:01 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
// For short lookups, check that the word has been shifted by the correct number of bits.
|
2022-05-08 20:27:03 -07:00
|
|
|
// https://p.z.cash/halo2-0.1:decompose-short-lookup
|
2021-06-24 03:23:01 -07:00
|
|
|
meta.create_gate("Short lookup bitshift", |meta| {
|
2021-07-26 21:15:21 -07:00
|
|
|
let q_bitshift = meta.query_selector(config.q_bitshift);
|
2021-06-24 03:23:01 -07:00
|
|
|
let word = meta.query_advice(config.running_sum, Rotation::prev());
|
|
|
|
let shifted_word = meta.query_advice(config.running_sum, Rotation::cur());
|
2021-07-23 09:31:01 -07:00
|
|
|
let inv_two_pow_s = meta.query_advice(config.running_sum, Rotation::next());
|
2021-06-24 03:23:01 -07:00
|
|
|
|
2021-12-07 09:47:03 -08:00
|
|
|
let two_pow_k = F::from(1 << K);
|
2021-06-24 03:23:01 -07:00
|
|
|
|
|
|
|
// shifted_word = word * 2^{K-s}
|
|
|
|
// = word * 2^K * inv_two_pow_s
|
2022-04-24 15:13:38 -07:00
|
|
|
Constraints::with_selector(
|
|
|
|
q_bitshift,
|
|
|
|
Some(word * two_pow_k * inv_two_pow_s - shifted_word),
|
|
|
|
)
|
2021-06-24 03:23:01 -07:00
|
|
|
});
|
|
|
|
|
2021-06-12 05:21:55 -07:00
|
|
|
config
|
|
|
|
}
|
|
|
|
|
2021-06-13 08:20:13 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
// Loads the values [0..2^K) into `table_idx`. This is only used in testing
|
|
|
|
// for now, since the Sinsemilla chip provides a pre-loaded table in the
|
|
|
|
// Orchard context.
|
2021-06-24 03:23:01 -07:00
|
|
|
pub fn load(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
|
2021-07-27 10:32:32 -07:00
|
|
|
layouter.assign_table(
|
2021-06-12 05:21:55 -07:00
|
|
|
|| "table_idx",
|
2021-07-27 10:32:32 -07:00
|
|
|
|mut table| {
|
2021-06-12 05:21:55 -07:00
|
|
|
// We generate the row values lazily (we only need them during keygen).
|
|
|
|
for index in 0..(1 << K) {
|
2021-07-27 10:32:32 -07:00
|
|
|
table.assign_cell(
|
2021-06-12 05:21:55 -07:00
|
|
|
|| "table_idx",
|
2021-06-12 18:40:50 -07:00
|
|
|
self.table_idx,
|
2021-06-12 05:21:55 -07:00
|
|
|
index,
|
2022-06-08 13:37:52 -07:00
|
|
|
|| Value::known(F::from(index as u64)),
|
2021-06-12 05:21:55 -07:00
|
|
|
)?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-06-24 03:23:01 -07:00
|
|
|
/// Range check on an existing cell that is copied into this helper.
|
2021-07-15 04:52:15 -07:00
|
|
|
///
|
|
|
|
/// Returns an error if `element` is not in a column that was passed to
|
|
|
|
/// [`ConstraintSystem::enable_equality`] during circuit configuration.
|
2021-06-24 03:23:01 -07:00
|
|
|
pub fn copy_check(
|
|
|
|
&self,
|
|
|
|
mut layouter: impl Layouter<F>,
|
2021-12-01 18:51:46 -08:00
|
|
|
element: AssignedCell<F, F>,
|
2021-06-24 03:23:01 -07:00
|
|
|
num_words: usize,
|
|
|
|
strict: bool,
|
2021-07-24 08:54:54 -07:00
|
|
|
) -> Result<RunningSum<F>, Error> {
|
2021-06-24 03:23:01 -07:00
|
|
|
layouter.assign_region(
|
|
|
|
|| format!("{:?} words range check", num_words),
|
|
|
|
|mut region| {
|
|
|
|
// Copy `element` and initialize running sum `z_0 = element` to decompose it.
|
2021-12-01 18:10:00 -08:00
|
|
|
let z_0 = element.copy_advice(|| "z_0", &mut region, self.running_sum, 0)?;
|
2021-07-24 08:54:54 -07:00
|
|
|
self.range_check(&mut region, z_0, num_words, strict)
|
2021-06-24 03:23:01 -07:00
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Range check on a value that is witnessed in this helper.
|
|
|
|
pub fn witness_check(
|
|
|
|
&self,
|
|
|
|
mut layouter: impl Layouter<F>,
|
2022-06-08 13:37:52 -07:00
|
|
|
value: Value<F>,
|
2021-06-24 03:23:01 -07:00
|
|
|
num_words: usize,
|
|
|
|
strict: bool,
|
2021-07-24 08:54:54 -07:00
|
|
|
) -> Result<RunningSum<F>, Error> {
|
2021-07-22 07:14:34 -07:00
|
|
|
layouter.assign_region(
|
2021-06-24 03:23:01 -07:00
|
|
|
|| "Witness element",
|
|
|
|
|mut region| {
|
2022-06-08 13:37:52 -07:00
|
|
|
let z_0 =
|
|
|
|
region.assign_advice(|| "Witness element", self.running_sum, 0, || value)?;
|
2021-07-24 08:54:54 -07:00
|
|
|
self.range_check(&mut region, z_0, num_words, strict)
|
2021-06-24 03:23:01 -07:00
|
|
|
},
|
2021-07-22 07:14:34 -07:00
|
|
|
)
|
2021-06-24 03:23:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// If `strict` is set to "true", the field element must fit into
|
|
|
|
/// `num_words * K` bits. In other words, the the final cumulative sum `z_{num_words}`
|
|
|
|
/// must be zero.
|
|
|
|
///
|
|
|
|
/// If `strict` is set to "false", the final `z_{num_words}` is not constrained.
|
|
|
|
///
|
|
|
|
/// `element` must have been assigned to `self.running_sum` at offset 0.
|
|
|
|
fn range_check(
|
2021-06-12 05:21:55 -07:00
|
|
|
&self,
|
2021-06-12 18:40:50 -07:00
|
|
|
region: &mut Region<'_, F>,
|
2021-12-01 18:51:46 -08:00
|
|
|
element: AssignedCell<F, F>,
|
2021-06-12 05:21:55 -07:00
|
|
|
num_words: usize,
|
2021-07-22 07:14:34 -07:00
|
|
|
strict: bool,
|
2021-07-24 08:54:54 -07:00
|
|
|
) -> Result<RunningSum<F>, Error> {
|
2021-06-12 05:21:55 -07:00
|
|
|
// `num_words` must fit into a single field element.
|
2021-06-14 09:16:19 -07:00
|
|
|
assert!(num_words * K <= F::CAPACITY as usize);
|
2021-06-12 05:21:55 -07:00
|
|
|
let num_bits = num_words * K;
|
|
|
|
|
2021-06-13 08:20:13 -07:00
|
|
|
// Chunk the first num_bits bits into K-bit words.
|
2021-06-14 09:16:19 -07:00
|
|
|
let words = {
|
|
|
|
// Take first num_bits bits of `element`.
|
|
|
|
let bits = element.value().map(|element| {
|
|
|
|
element
|
|
|
|
.to_le_bits()
|
|
|
|
.into_iter()
|
|
|
|
.take(num_bits)
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
});
|
|
|
|
|
2022-06-08 13:37:52 -07:00
|
|
|
bits.map(|bits| {
|
2021-06-14 09:16:19 -07:00
|
|
|
bits.chunks_exact(K)
|
2021-12-07 09:47:03 -08:00
|
|
|
.map(|word| F::from(lebs2ip::<K>(&(word.try_into().unwrap()))))
|
2021-06-14 09:16:19 -07:00
|
|
|
.collect::<Vec<_>>()
|
2022-06-08 13:37:52 -07:00
|
|
|
})
|
|
|
|
.transpose_vec(num_words)
|
2021-06-12 05:21:55 -07:00
|
|
|
};
|
|
|
|
|
2021-12-01 04:51:33 -08:00
|
|
|
let mut zs = vec![element.clone()];
|
2021-06-12 18:40:50 -07:00
|
|
|
|
|
|
|
// Assign cumulative sum such that
|
|
|
|
// z_i = 2^{K}⋅z_{i + 1} + a_i
|
|
|
|
// => z_{i + 1} = (z_i - a_i) / (2^K)
|
|
|
|
//
|
2021-06-13 08:20:13 -07:00
|
|
|
// For `element` = a_0 + 2^10 a_1 + ... + 2^{120} a_{12}}, initialize z_0 = `element`.
|
|
|
|
// If `element` fits in 130 bits, we end up with z_{13} = 0.
|
2021-06-24 03:23:01 -07:00
|
|
|
let mut z = element;
|
2021-12-07 09:47:03 -08:00
|
|
|
let inv_two_pow_k = F::from(1u64 << K).invert().unwrap();
|
2021-06-24 03:23:01 -07:00
|
|
|
for (idx, word) in words.iter().enumerate() {
|
2021-07-26 21:15:21 -07:00
|
|
|
// Enable q_lookup on this row
|
2021-06-24 03:23:01 -07:00
|
|
|
self.q_lookup.enable(region, idx)?;
|
2021-07-26 21:15:21 -07:00
|
|
|
// Enable q_running on this row
|
|
|
|
self.q_running.enable(region, idx)?;
|
2021-06-24 03:23:01 -07:00
|
|
|
|
2021-06-12 18:40:50 -07:00
|
|
|
// z_next = (z_cur - m_cur) / 2^K
|
|
|
|
z = {
|
|
|
|
let z_val = z
|
|
|
|
.value()
|
2021-06-24 03:23:01 -07:00
|
|
|
.zip(*word)
|
2021-12-01 16:10:00 -08:00
|
|
|
.map(|(z, word)| (*z - word) * inv_two_pow_k);
|
2021-06-12 18:40:50 -07:00
|
|
|
|
|
|
|
// Assign z_next
|
2021-12-01 17:10:00 -08:00
|
|
|
region.assign_advice(
|
2021-06-12 18:40:50 -07:00
|
|
|
|| format!("z_{:?}", idx + 1),
|
|
|
|
self.running_sum,
|
2021-06-24 03:23:01 -07:00
|
|
|
idx + 1,
|
2022-06-08 13:37:52 -07:00
|
|
|
|| z_val,
|
2021-12-01 17:10:00 -08:00
|
|
|
)?
|
2021-06-12 18:40:50 -07:00
|
|
|
};
|
2021-12-01 04:51:33 -08:00
|
|
|
zs.push(z.clone());
|
2021-06-12 18:40:50 -07:00
|
|
|
}
|
2021-06-12 05:21:55 -07:00
|
|
|
|
2021-07-22 07:14:34 -07:00
|
|
|
if strict {
|
|
|
|
// Constrain the final `z` to be zero.
|
2022-11-29 21:05:37 -08:00
|
|
|
region.constrain_constant(zs.last().unwrap().cell(), F::ZERO)?;
|
2021-07-22 07:14:34 -07:00
|
|
|
}
|
|
|
|
|
2021-07-24 08:54:54 -07:00
|
|
|
Ok(RunningSum(zs))
|
2021-06-12 05:21:55 -07:00
|
|
|
}
|
2021-06-24 03:23:01 -07:00
|
|
|
|
|
|
|
/// Short range check on an existing cell that is copied into this helper.
|
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
///
|
|
|
|
/// Panics if NUM_BITS is equal to or larger than K.
|
|
|
|
pub fn copy_short_check(
|
|
|
|
&self,
|
|
|
|
mut layouter: impl Layouter<F>,
|
2021-12-01 18:51:46 -08:00
|
|
|
element: AssignedCell<F, F>,
|
2021-06-24 03:23:01 -07:00
|
|
|
num_bits: usize,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
assert!(num_bits < K);
|
|
|
|
layouter.assign_region(
|
|
|
|
|| format!("Range check {:?} bits", num_bits),
|
|
|
|
|mut region| {
|
|
|
|
// Copy `element` to use in the k-bit lookup.
|
2021-12-01 18:10:00 -08:00
|
|
|
let element =
|
|
|
|
element.copy_advice(|| "element", &mut region, self.running_sum, 0)?;
|
2021-06-24 03:23:01 -07:00
|
|
|
|
|
|
|
self.short_range_check(&mut region, element, num_bits)
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Short range check on value that is witnessed in this helper.
|
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
///
|
|
|
|
/// Panics if num_bits is larger than K.
|
|
|
|
pub fn witness_short_check(
|
|
|
|
&self,
|
|
|
|
mut layouter: impl Layouter<F>,
|
2022-06-08 13:37:52 -07:00
|
|
|
element: Value<F>,
|
2021-06-24 03:23:01 -07:00
|
|
|
num_bits: usize,
|
2021-12-01 17:10:00 -08:00
|
|
|
) -> Result<AssignedCell<F, F>, Error> {
|
2021-06-24 03:23:01 -07:00
|
|
|
assert!(num_bits <= K);
|
|
|
|
layouter.assign_region(
|
|
|
|
|| format!("Range check {:?} bits", num_bits),
|
|
|
|
|mut region| {
|
|
|
|
// Witness `element` to use in the k-bit lookup.
|
2022-06-08 13:37:52 -07:00
|
|
|
let element =
|
|
|
|
region.assign_advice(|| "Witness element", self.running_sum, 0, || element)?;
|
2021-06-24 03:23:01 -07:00
|
|
|
|
2021-12-01 04:51:33 -08:00
|
|
|
self.short_range_check(&mut region, element.clone(), num_bits)?;
|
2021-06-24 03:23:01 -07:00
|
|
|
|
|
|
|
Ok(element)
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Constrain `x` to be a NUM_BITS word.
|
|
|
|
///
|
|
|
|
/// `element` must have been assigned to `self.running_sum` at offset 0.
|
|
|
|
fn short_range_check(
|
|
|
|
&self,
|
|
|
|
region: &mut Region<'_, F>,
|
2021-12-01 18:51:46 -08:00
|
|
|
element: AssignedCell<F, F>,
|
2021-06-24 03:23:01 -07:00
|
|
|
num_bits: usize,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
// Enable lookup for `element`, to constrain it to 10 bits.
|
2021-07-26 21:15:21 -07:00
|
|
|
self.q_lookup.enable(region, 0)?;
|
2021-06-24 03:23:01 -07:00
|
|
|
|
2021-07-23 09:31:01 -07:00
|
|
|
// Enable lookup for shifted element, to constrain it to 10 bits.
|
2021-07-26 21:15:21 -07:00
|
|
|
self.q_lookup.enable(region, 1)?;
|
2021-07-23 09:31:01 -07:00
|
|
|
|
|
|
|
// Check element has been shifted by the correct number of bits.
|
2021-07-26 21:15:21 -07:00
|
|
|
self.q_bitshift.enable(region, 1)?;
|
2021-06-24 03:23:01 -07:00
|
|
|
|
|
|
|
// Assign shifted `element * 2^{K - num_bits}`
|
2022-06-08 13:37:52 -07:00
|
|
|
let shifted = element.value().into_field() * F::from(1 << (K - num_bits));
|
2021-06-24 03:23:01 -07:00
|
|
|
|
|
|
|
region.assign_advice(
|
|
|
|
|| format!("element * 2^({}-{})", K, num_bits),
|
|
|
|
self.running_sum,
|
|
|
|
1,
|
2022-06-08 13:37:52 -07:00
|
|
|
|| shifted,
|
2021-06-24 03:23:01 -07:00
|
|
|
)?;
|
|
|
|
|
2021-07-23 09:31:01 -07:00
|
|
|
// Assign 2^{-num_bits} from a fixed column.
|
2021-12-07 09:47:03 -08:00
|
|
|
let inv_two_pow_s = F::from(1 << num_bits).invert().unwrap();
|
2021-07-23 09:31:01 -07:00
|
|
|
region.assign_advice_from_constant(
|
|
|
|
|| format!("2^(-{})", num_bits),
|
|
|
|
self.running_sum,
|
|
|
|
2,
|
|
|
|
inv_two_pow_s,
|
|
|
|
)?;
|
2021-06-24 03:23:01 -07:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-06-12 05:21:55 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2021-06-12 18:40:50 -07:00
|
|
|
use super::LookupRangeCheckConfig;
|
2021-06-13 08:20:13 -07:00
|
|
|
|
2021-12-20 23:14:21 -08:00
|
|
|
use super::super::lebs2ip;
|
2022-05-08 19:54:04 -07:00
|
|
|
use crate::sinsemilla::primitives::K;
|
|
|
|
|
2021-06-24 03:23:01 -07:00
|
|
|
use ff::{Field, PrimeFieldBits};
|
2022-01-27 15:28:02 -08:00
|
|
|
use halo2_proofs::{
|
2022-06-08 13:37:52 -07:00
|
|
|
circuit::{Layouter, SimpleFloorPlanner, Value},
|
2022-01-04 23:02:21 -08:00
|
|
|
dev::{FailureLocation, MockProver, VerifyFailure},
|
2021-07-08 18:56:27 -07:00
|
|
|
plonk::{Circuit, ConstraintSystem, Error},
|
2021-06-12 05:21:55 -07:00
|
|
|
};
|
2022-11-29 16:12:17 -08:00
|
|
|
use pasta_curves::pallas;
|
2021-06-12 05:21:55 -07:00
|
|
|
|
2021-06-13 08:20:13 -07:00
|
|
|
use std::{convert::TryInto, marker::PhantomData};
|
2021-06-12 05:21:55 -07:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn lookup_range_check() {
|
2021-07-08 18:56:27 -07:00
|
|
|
#[derive(Clone, Copy)]
|
2022-11-29 16:12:17 -08:00
|
|
|
struct MyCircuit<F: PrimeFieldBits> {
|
2021-06-24 03:23:01 -07:00
|
|
|
num_words: usize,
|
2021-06-12 05:21:55 -07:00
|
|
|
_marker: PhantomData<F>,
|
|
|
|
}
|
|
|
|
|
2022-11-29 16:12:17 -08:00
|
|
|
impl<F: PrimeFieldBits> Circuit<F> for MyCircuit<F> {
|
2021-06-12 18:40:50 -07:00
|
|
|
type Config = LookupRangeCheckConfig<F, K>;
|
2021-07-08 18:56:27 -07:00
|
|
|
type FloorPlanner = SimpleFloorPlanner;
|
|
|
|
|
|
|
|
fn without_witnesses(&self) -> Self {
|
|
|
|
*self
|
|
|
|
}
|
2021-06-12 05:21:55 -07:00
|
|
|
|
|
|
|
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
|
|
|
|
let running_sum = meta.advice_column();
|
2021-07-27 10:32:32 -07:00
|
|
|
let table_idx = meta.lookup_table_column();
|
2021-07-20 01:22:08 -07:00
|
|
|
let constants = meta.fixed_column();
|
|
|
|
meta.enable_constant(constants);
|
2021-07-15 04:52:15 -07:00
|
|
|
|
2021-07-20 01:22:08 -07:00
|
|
|
LookupRangeCheckConfig::<F, K>::configure(meta, running_sum, table_idx)
|
2021-06-12 05:21:55 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn synthesize(
|
|
|
|
&self,
|
|
|
|
config: Self::Config,
|
2021-07-08 18:56:27 -07:00
|
|
|
mut layouter: impl Layouter<F>,
|
2021-06-12 05:21:55 -07:00
|
|
|
) -> Result<(), Error> {
|
|
|
|
// Load table_idx
|
2021-06-12 18:40:50 -07:00
|
|
|
config.load(&mut layouter)?;
|
2021-06-12 05:21:55 -07:00
|
|
|
|
2021-06-24 03:23:01 -07:00
|
|
|
// Lookup constraining element to be no longer than num_words * K bits.
|
2021-06-13 08:20:13 -07:00
|
|
|
let elements_and_expected_final_zs = [
|
2022-11-29 21:05:37 -08:00
|
|
|
(F::from((1 << (self.num_words * K)) - 1), F::ZERO, true), // a word that is within self.num_words * K bits long
|
|
|
|
(F::from(1 << (self.num_words * K)), F::ONE, false), // a word that is just over self.num_words * K bits long
|
2021-06-12 18:40:50 -07:00
|
|
|
];
|
2021-06-12 05:21:55 -07:00
|
|
|
|
2022-11-29 16:12:17 -08:00
|
|
|
fn expected_zs<F: PrimeFieldBits, const K: usize>(
|
2021-06-24 03:23:01 -07:00
|
|
|
element: F,
|
|
|
|
num_words: usize,
|
|
|
|
) -> Vec<F> {
|
|
|
|
let chunks = {
|
|
|
|
element
|
|
|
|
.to_le_bits()
|
|
|
|
.iter()
|
2022-05-04 16:36:18 -07:00
|
|
|
.by_vals()
|
2021-06-24 03:23:01 -07:00
|
|
|
.take(num_words * K)
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
.chunks_exact(K)
|
2021-12-07 09:47:03 -08:00
|
|
|
.map(|chunk| F::from(lebs2ip::<K>(chunk.try_into().unwrap())))
|
2021-06-24 03:23:01 -07:00
|
|
|
.collect::<Vec<_>>()
|
|
|
|
};
|
|
|
|
let expected_zs = {
|
2021-12-15 07:24:01 -08:00
|
|
|
let inv_two_pow_k = F::from(1 << K).invert().unwrap();
|
2021-06-24 03:23:01 -07:00
|
|
|
chunks.iter().fold(vec![element], |mut zs, a_i| {
|
|
|
|
// z_{i + 1} = (z_i - a_i) / 2^{K}
|
|
|
|
let z = (zs[zs.len() - 1] - a_i) * inv_two_pow_k;
|
|
|
|
zs.push(z);
|
|
|
|
zs
|
|
|
|
})
|
|
|
|
};
|
|
|
|
expected_zs
|
|
|
|
}
|
2021-06-12 05:21:55 -07:00
|
|
|
|
2021-06-24 03:23:01 -07:00
|
|
|
for (element, expected_final_z, strict) in elements_and_expected_final_zs.iter() {
|
|
|
|
let expected_zs = expected_zs::<F, K>(*element, self.num_words);
|
2021-06-12 05:21:55 -07:00
|
|
|
|
2021-07-24 08:54:54 -07:00
|
|
|
let zs = config.witness_check(
|
2021-06-24 03:23:01 -07:00
|
|
|
layouter.namespace(|| format!("Lookup {:?}", self.num_words)),
|
2022-06-08 13:37:52 -07:00
|
|
|
Value::known(*element),
|
2021-06-24 03:23:01 -07:00
|
|
|
self.num_words,
|
|
|
|
*strict,
|
2021-06-12 05:21:55 -07:00
|
|
|
)?;
|
|
|
|
|
2021-06-13 08:20:13 -07:00
|
|
|
assert_eq!(*expected_zs.last().unwrap(), *expected_final_z);
|
2021-06-12 05:21:55 -07:00
|
|
|
|
|
|
|
for (expected_z, z) in expected_zs.into_iter().zip(zs.iter()) {
|
2022-06-08 13:37:52 -07:00
|
|
|
z.value().assert_if_known(|z| &&expected_z == z);
|
2021-06-12 05:21:55 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
let circuit: MyCircuit<pallas::Base> = MyCircuit {
|
2021-06-24 03:23:01 -07:00
|
|
|
num_words: 6,
|
2021-06-12 05:21:55 -07:00
|
|
|
_marker: PhantomData,
|
|
|
|
};
|
2021-07-20 01:22:08 -07:00
|
|
|
|
2021-06-12 05:21:55 -07:00
|
|
|
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();
|
|
|
|
assert_eq!(prover.verify(), Ok(()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-24 03:23:01 -07:00
|
|
|
#[test]
|
|
|
|
fn short_range_check() {
|
2022-11-29 16:12:17 -08:00
|
|
|
struct MyCircuit<F: PrimeFieldBits> {
|
2022-06-08 13:37:52 -07:00
|
|
|
element: Value<F>,
|
2021-06-24 03:23:01 -07:00
|
|
|
num_bits: usize,
|
|
|
|
}
|
|
|
|
|
2022-11-29 16:12:17 -08:00
|
|
|
impl<F: PrimeFieldBits> Circuit<F> for MyCircuit<F> {
|
2021-06-24 03:23:01 -07:00
|
|
|
type Config = LookupRangeCheckConfig<F, K>;
|
2021-07-08 18:56:27 -07:00
|
|
|
type FloorPlanner = SimpleFloorPlanner;
|
|
|
|
|
|
|
|
fn without_witnesses(&self) -> Self {
|
|
|
|
MyCircuit {
|
2022-06-08 13:37:52 -07:00
|
|
|
element: Value::unknown(),
|
2021-07-08 18:56:27 -07:00
|
|
|
num_bits: self.num_bits,
|
|
|
|
}
|
|
|
|
}
|
2021-06-24 03:23:01 -07:00
|
|
|
|
|
|
|
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
|
|
|
|
let running_sum = meta.advice_column();
|
2021-07-27 10:32:32 -07:00
|
|
|
let table_idx = meta.lookup_table_column();
|
2021-07-20 01:22:08 -07:00
|
|
|
let constants = meta.fixed_column();
|
|
|
|
meta.enable_constant(constants);
|
2021-07-15 04:52:15 -07:00
|
|
|
|
2021-07-20 01:22:08 -07:00
|
|
|
LookupRangeCheckConfig::<F, K>::configure(meta, running_sum, table_idx)
|
2021-06-24 03:23:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn synthesize(
|
|
|
|
&self,
|
|
|
|
config: Self::Config,
|
2021-07-08 18:56:27 -07:00
|
|
|
mut layouter: impl Layouter<F>,
|
2021-06-24 03:23:01 -07:00
|
|
|
) -> Result<(), Error> {
|
|
|
|
// Load table_idx
|
|
|
|
config.load(&mut layouter)?;
|
|
|
|
|
|
|
|
// Lookup constraining element to be no longer than num_bits.
|
|
|
|
config.witness_short_check(
|
|
|
|
layouter.namespace(|| format!("Lookup {:?} bits", self.num_bits)),
|
|
|
|
self.element,
|
|
|
|
self.num_bits,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Edge case: zero bits
|
|
|
|
{
|
|
|
|
let circuit: MyCircuit<pallas::Base> = MyCircuit {
|
2022-11-29 21:05:37 -08:00
|
|
|
element: Value::known(pallas::Base::ZERO),
|
2021-06-24 03:23:01 -07:00
|
|
|
num_bits: 0,
|
|
|
|
};
|
|
|
|
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();
|
|
|
|
assert_eq!(prover.verify(), Ok(()));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Edge case: K bits
|
|
|
|
{
|
|
|
|
let circuit: MyCircuit<pallas::Base> = MyCircuit {
|
2022-06-08 13:37:52 -07:00
|
|
|
element: Value::known(pallas::Base::from((1 << K) - 1)),
|
2021-06-24 03:23:01 -07:00
|
|
|
num_bits: K,
|
|
|
|
};
|
|
|
|
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();
|
|
|
|
assert_eq!(prover.verify(), Ok(()));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Element within `num_bits`
|
|
|
|
{
|
|
|
|
let circuit: MyCircuit<pallas::Base> = MyCircuit {
|
2022-06-08 13:37:52 -07:00
|
|
|
element: Value::known(pallas::Base::from((1 << 6) - 1)),
|
2021-06-24 03:23:01 -07:00
|
|
|
num_bits: 6,
|
|
|
|
};
|
|
|
|
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();
|
|
|
|
assert_eq!(prover.verify(), Ok(()));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Element larger than `num_bits` but within K bits
|
|
|
|
{
|
|
|
|
let circuit: MyCircuit<pallas::Base> = MyCircuit {
|
2022-06-08 13:37:52 -07:00
|
|
|
element: Value::known(pallas::Base::from(1 << 6)),
|
2021-06-24 03:23:01 -07:00
|
|
|
num_bits: 6,
|
|
|
|
};
|
|
|
|
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
prover.verify(),
|
|
|
|
Err(vec![VerifyFailure::Lookup {
|
2021-07-26 21:15:21 -07:00
|
|
|
lookup_index: 0,
|
2022-01-04 23:02:21 -08:00
|
|
|
location: FailureLocation::InRegion {
|
|
|
|
region: (1, "Range check 6 bits").into(),
|
|
|
|
offset: 1,
|
|
|
|
},
|
2021-06-24 03:23:01 -07:00
|
|
|
}])
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Element larger than K bits
|
|
|
|
{
|
|
|
|
let circuit: MyCircuit<pallas::Base> = MyCircuit {
|
2022-06-08 13:37:52 -07:00
|
|
|
element: Value::known(pallas::Base::from(1 << K)),
|
2021-06-24 03:23:01 -07:00
|
|
|
num_bits: 6,
|
|
|
|
};
|
|
|
|
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
prover.verify(),
|
|
|
|
Err(vec![
|
|
|
|
VerifyFailure::Lookup {
|
2021-07-26 21:15:21 -07:00
|
|
|
lookup_index: 0,
|
2022-01-04 23:02:21 -08:00
|
|
|
location: FailureLocation::InRegion {
|
|
|
|
region: (1, "Range check 6 bits").into(),
|
|
|
|
offset: 0,
|
|
|
|
},
|
2021-06-24 03:23:01 -07:00
|
|
|
},
|
|
|
|
VerifyFailure::Lookup {
|
2021-07-26 21:15:21 -07:00
|
|
|
lookup_index: 0,
|
2022-01-04 23:02:21 -08:00
|
|
|
location: FailureLocation::InRegion {
|
|
|
|
region: (1, "Range check 6 bits").into(),
|
|
|
|
offset: 1,
|
|
|
|
},
|
2021-06-24 03:23:01 -07:00
|
|
|
},
|
|
|
|
])
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Element which is not within `num_bits`, but which has a shifted value within
|
|
|
|
// num_bits
|
|
|
|
{
|
|
|
|
let num_bits = 6;
|
2021-12-07 09:47:03 -08:00
|
|
|
let shifted = pallas::Base::from((1 << num_bits) - 1);
|
2021-06-24 03:23:01 -07:00
|
|
|
// Recall that shifted = element * 2^{K-s}
|
|
|
|
// => element = shifted * 2^{s-K}
|
|
|
|
let element = shifted
|
2021-12-07 09:47:03 -08:00
|
|
|
* pallas::Base::from(1 << (K as u64 - num_bits))
|
2021-06-24 03:23:01 -07:00
|
|
|
.invert()
|
|
|
|
.unwrap();
|
|
|
|
let circuit: MyCircuit<pallas::Base> = MyCircuit {
|
2022-06-08 13:37:52 -07:00
|
|
|
element: Value::known(element),
|
2021-06-24 03:23:01 -07:00
|
|
|
num_bits: num_bits as usize,
|
|
|
|
};
|
|
|
|
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
prover.verify(),
|
|
|
|
Err(vec![VerifyFailure::Lookup {
|
2021-07-26 21:15:21 -07:00
|
|
|
lookup_index: 0,
|
2022-01-04 23:02:21 -08:00
|
|
|
location: FailureLocation::InRegion {
|
|
|
|
region: (1, "Range check 6 bits").into(),
|
|
|
|
offset: 0,
|
|
|
|
},
|
2021-06-24 03:23:01 -07:00
|
|
|
}])
|
|
|
|
);
|
|
|
|
}
|
2021-06-12 05:21:55 -07:00
|
|
|
}
|
|
|
|
}
|