use crate::util::{and, not, Expr};
use eth_types::Field;
use halo2_proofs::{
circuit::{Region, Value},
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, VirtualCells},
poly::Rotation,
};
use std::{collections::BTreeSet, marker::PhantomData, ops::Deref};
use strum::IntoEnumIterator;
pub trait AsBits<const N: usize> {
fn as_bits(&self) -> [bool; N];
}
impl<T, const N: usize> AsBits<N> for T
where
T: Copy + Into<usize>,
{
fn as_bits(&self) -> [bool; N] {
let mut bits = [false; N];
let mut x: usize = (*self).into();
for i in 0..N {
bits[N - 1 - i] = x % 2 == 1;
x /= 2;
}
bits
}
}
#[derive(Clone, Copy, Debug)]
pub struct BinaryNumberBits<const N: usize>(
pub [Column<Advice>; N],
);
impl<const N: usize> Deref for BinaryNumberBits<N> {
type Target = [Column<Advice>; N];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<const N: usize> BinaryNumberBits<N> {
pub fn construct<F: Field>(meta: &mut ConstraintSystem<F>) -> Self {
Self([0; N].map(|_| meta.advice_column()))
}
pub fn assign<F: Field, T: AsBits<N>>(
&self,
region: &mut Region<'_, F>,
offset: usize,
value: &T,
) -> Result<(), Error> {
for (&bit, &column) in value.as_bits().iter().zip(self.iter()) {
region.assign_advice(
|| format!("binary number {:?}", column),
column,
offset,
|| Value::known(F::from(bit as u64)),
)?;
}
Ok(())
}
pub fn value<F: Field>(
&self,
rotation: Rotation,
) -> impl FnOnce(&mut VirtualCells<'_, F>) -> Expression<F> {
let bits = self.0;
move |meta: &mut VirtualCells<'_, F>| {
let bits = bits.map(|bit| meta.query_advice(bit, rotation));
bits.iter()
.fold(0.expr(), |result, bit| bit.clone() + result * 2.expr())
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct BinaryNumberConfig<T, const N: usize> {
pub bits: BinaryNumberBits<N>,
_marker: PhantomData<T>,
}
impl<T, const N: usize> BinaryNumberConfig<T, N>
where
T: AsBits<N>,
{
pub fn value<F: Field>(
&self,
rotation: Rotation,
) -> impl FnOnce(&mut VirtualCells<'_, F>) -> Expression<F> {
self.bits.value(rotation)
}
pub fn constant_expr<F: Field>(&self, value: T) -> Expression<F> {
let f = value.as_bits().iter().fold(
F::ZERO,
|result, bit| if *bit { F::ONE } else { F::ZERO } + result * F::from(2),
);
Expression::Constant(f)
}
pub fn value_equals<F: Field, S: AsBits<N>>(
&self,
value: S,
rotation: Rotation,
) -> impl FnOnce(&mut VirtualCells<'_, F>) -> Expression<F> {
let bits = self.bits;
move |meta| Self::value_equals_expr(value, bits.map(|bit| meta.query_advice(bit, rotation)))
}
pub fn value_equals_expr<F: Field, S: AsBits<N>>(
value: S,
expressions: [Expression<F>; N], ) -> Expression<F> {
and::expr(
value
.as_bits()
.iter()
.zip(&expressions)
.map(|(&bit, expression)| {
if bit {
expression.clone()
} else {
not::expr(expression.clone())
}
}),
)
}
pub fn annotate_columns_in_region<F: Field>(&self, region: &mut Region<F>, prefix: &str) {
let mut annotations = Vec::new();
for (i, _) in self.bits.iter().enumerate() {
annotations.push(format!("GADGETS_binary_number_{}", i));
}
self.bits
.iter()
.zip(annotations.iter())
.for_each(|(col, ann)| region.name_column(|| format!("{}_{}", prefix, ann), *col));
}
}
#[derive(Clone, Debug)]
pub struct BinaryNumberChip<F, T, const N: usize> {
config: BinaryNumberConfig<T, N>,
_marker: PhantomData<F>,
}
impl<F: Field, T: IntoEnumIterator, const N: usize> BinaryNumberChip<F, T, N>
where
T: AsBits<N>,
{
pub fn construct(config: BinaryNumberConfig<T, N>) -> Self {
Self {
config,
_marker: PhantomData,
}
}
pub fn configure(
meta: &mut ConstraintSystem<F>,
bits: BinaryNumberBits<N>,
selector: Column<Fixed>,
value: Option<Column<Advice>>,
) -> BinaryNumberConfig<T, N> {
bits.map(|bit| {
meta.create_gate("bit column is 0 or 1", |meta| {
let selector = meta.query_fixed(selector, Rotation::cur());
let bit = meta.query_advice(bit, Rotation::cur());
vec![selector * bit.clone() * (1.expr() - bit)]
})
});
let config = BinaryNumberConfig {
bits,
_marker: PhantomData,
};
if let Some(value) = value {
meta.create_gate("binary number value", |meta| {
let selector = meta.query_fixed(selector, Rotation::cur());
vec![
selector
* (config.value(Rotation::cur())(meta)
- meta.query_advice(value, Rotation::cur())),
]
});
}
let valid_values: BTreeSet<usize> = T::iter().map(|t| from_bits(&t.as_bits())).collect();
let mut invalid_values = (0..1 << N).filter(|i| !valid_values.contains(i)).peekable();
if invalid_values.peek().is_some() {
meta.create_gate("binary number value in range", |meta| {
let selector = meta.query_fixed(selector, Rotation::cur());
invalid_values
.map(|i| {
let value_equals_i = config.value_equals(i, Rotation::cur());
selector.clone() * value_equals_i(meta)
})
.collect::<Vec<_>>()
});
}
config
}
pub fn assign(
&self,
region: &mut Region<'_, F>,
offset: usize,
value: &T,
) -> Result<(), Error> {
self.config.bits.assign(region, offset, value)
}
}
pub fn from_bits(bits: &[bool]) -> usize {
bits.iter()
.fold(0, |result, &bit| bit as usize + 2 * result)
}