use {
crate::{
digits::{add_digital_decomposition, DigitalDecompositionWitnessesBuilder},
noir_to_r1cs::NoirToR1CSCompiler,
},
ark_ff::{AdditiveGroup, Field},
provekit_common::{
witness::{ConstantTerm, SumTerm, WitnessBuilder},
FieldElement,
},
std::collections::{BTreeMap, HashMap},
};
#[derive(Clone, Copy, Debug)]
pub(crate) struct U8 {
pub(crate) idx: usize,
pub(crate) range_checked: bool,
}
impl U8 {
pub(crate) fn new(idx: usize, range_checked: bool) -> Self {
Self { idx, range_checked }
}
pub(crate) fn zero(r1cs_compiler: &mut NoirToR1CSCompiler) -> Self {
let idx = r1cs_compiler.num_witnesses();
r1cs_compiler.add_witness_builder(WitnessBuilder::Constant(ConstantTerm(
idx,
FieldElement::ZERO,
)));
Self {
idx,
range_checked: true,
}
}
pub(crate) fn from_const(r1cs_compiler: &mut NoirToR1CSCompiler, value: u8) -> Self {
let idx = r1cs_compiler.num_witnesses();
let value_fe = FieldElement::from(value as u64);
r1cs_compiler.add_witness_builder(WitnessBuilder::Constant(ConstantTerm(idx, value_fe)));
Self {
idx,
range_checked: true,
}
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct U32 {
pub(crate) bytes: [U8; 4],
}
impl U32 {
pub(crate) fn new(bytes: [U8; 4]) -> Self {
Self { bytes }
}
pub(crate) fn unpack_u32(
r1cs_compiler: &mut NoirToR1CSCompiler,
range_checks: &mut BTreeMap<u32, Vec<usize>>,
word: usize,
) -> U32 {
let log_bases = vec![8usize; 4];
let dd = add_digital_decomposition(r1cs_compiler, log_bases, vec![word]);
let bytes = (0..4)
.map(|i| {
let idx = dd.get_digit_witness_index(i, 0);
range_checks.entry(8).or_default().push(idx);
U8::new(idx, true)
})
.collect::<Vec<_>>()
.try_into()
.unwrap();
U32::new(bytes)
}
pub(crate) fn pack(
&self,
r1cs_compiler: &mut NoirToR1CSCompiler,
range_checks: &mut BTreeMap<u32, Vec<usize>>,
) -> usize {
let idx = r1cs_compiler.num_witnesses();
let mut terms = vec![];
let mut constraint_terms = vec![];
let mut multiplier = FieldElement::ONE;
for byte in self.bytes {
terms.push(SumTerm(Some(multiplier), byte.idx));
constraint_terms.push((multiplier, byte.idx));
multiplier *= FieldElement::from(256u64);
}
r1cs_compiler.add_witness_builder(WitnessBuilder::Sum(idx, terms));
r1cs_compiler.r1cs.add_constraint(
&constraint_terms,
&[(FieldElement::ONE, r1cs_compiler.witness_one())],
&[(FieldElement::ONE, idx)],
);
if !self.bytes.iter().all(|b| b.range_checked) {
range_checks.entry(32).or_default().push(idx);
}
idx
}
pub(crate) fn pack_cached(
&self,
r1cs_compiler: &mut NoirToR1CSCompiler,
range_checks: &mut BTreeMap<u32, Vec<usize>>,
pack_cache: &mut HashMap<[usize; 4], usize>,
) -> usize {
let key = [
self.bytes[0].idx,
self.bytes[1].idx,
self.bytes[2].idx,
self.bytes[3].idx,
];
if let Some(&cached_idx) = pack_cache.get(&key) {
return cached_idx;
}
let idx = self.pack(r1cs_compiler, range_checks);
pack_cache.insert(key, idx);
idx
}
pub(crate) fn from_const(r1cs_compiler: &mut NoirToR1CSCompiler, value: u32) -> Self {
let bytes = [
U8::from_const(r1cs_compiler, (value & 0xff) as u8),
U8::from_const(r1cs_compiler, ((value >> 8) & 0xff) as u8),
U8::from_const(r1cs_compiler, ((value >> 16) & 0xff) as u8),
U8::from_const(r1cs_compiler, ((value >> 24) & 0xff) as u8),
];
Self { bytes }
}
}