use midnight_proofs::{
circuit::{Layouter, Region, Value},
plonk::Error,
};
use num_bigint::BigUint;
use super::{ScannerChip, NB_SUBSTRING_COLS, PARSING_MAX_LEN_BITS, SUBSTRING_PARALLELISM};
use crate::{
field::AssignedNative,
instructions::{
ArithInstructions, AssertionInstructions, AssignmentInstructions, ControlFlowInstructions,
RangeCheckInstructions,
},
parsing::scanner::{Sequence, ALPHABET_MAX_SIZE},
types::AssignedByte,
CircuitField,
};
type SubstringCheckLayout<F> = Vec<[Sequence<F>; NB_SUBSTRING_COLS * SUBSTRING_PARALLELISM]>;
impl<F> ScannerChip<F>
where
F: CircuitField + Ord,
{
fn check_subsequence(
&self,
layouter: &mut impl Layouter<F>,
sequence: &[AssignedNative<F>],
idx: &AssignedNative<F>,
sub: &[AssignedNative<F>],
) -> Result<(), Error> {
if sub.is_empty() {
return Ok(());
}
self.native_gadget.assert_lower_than_fixed(
layouter,
idx,
&(BigUint::from(1u8) << PARSING_MAX_LEN_BITS),
)?;
self.sequence_cache
.borrow_mut()
.entry(sequence.to_vec())
.and_modify(|(calls, len)| {
*len += sub.len();
calls.push((idx.clone(), vec![], sub.to_vec(), None))
})
.or_insert_with(|| {
let sub_entry = (idx.clone(), vec![], sub.to_vec(), None);
(vec![sub_entry], sub.len())
});
Ok(())
}
pub fn check_bytes(
&self,
layouter: &mut impl Layouter<F>,
sequence: &[AssignedByte<F>],
idx: &AssignedNative<F>,
sub: &[AssignedByte<F>],
) -> Result<(), Error> {
let sequence: Sequence<F> = sequence.iter().map(AssignedNative::from).collect();
let sub: Sequence<F> = sub.iter().map(AssignedNative::from).collect();
self.check_subsequence(layouter, &sequence, idx, &sub)
}
pub fn check_bytes_ext(
&self,
layouter: &mut impl Layouter<F>,
sequence: &[AssignedNative<F>],
idx: &AssignedNative<F>,
sub: &[AssignedNative<F>],
) -> Result<(), Error> {
for x in sequence.iter().chain(sub) {
self.native_gadget.assert_lower_than_fixed(layouter, x, &257u16.into())?;
}
self.check_subsequence(layouter, sequence, idx, sub)
}
pub fn assert_equal_fixlen(
&self,
layouter: &mut impl Layouter<F>,
v1: &[AssignedByte<F>],
v2: &[AssignedByte<F>],
) -> Result<(), Error> {
assert_eq!(v1.len(), v2.len(), "byte slices must have equal length");
for (x, y) in v1.iter().zip(v2.iter()) {
self.native_gadget.assert_equal(layouter, x, y)?;
}
Ok(())
}
}
impl<F> ScannerChip<F>
where
F: CircuitField,
{
fn index_and_pack_sequence(
&self,
layouter: &mut impl Layouter<F>,
sequence: &[AssignedNative<F>],
start_idx: &AssignedNative<F>,
idx_offsets: &[(F, AssignedNative<F>)],
base_offset: u64,
) -> Result<Sequence<F>, Error> {
let shift = F::from(ALPHABET_MAX_SIZE as u64 + 1);
(sequence.iter().enumerate())
.map(|(i, byte)| {
let constant = F::from(i as u64 + base_offset);
let mut terms = vec![(shift, start_idx.clone()), (F::ONE, byte.clone())];
for (coeff, val) in idx_offsets {
terms.push((*coeff * shift, val.clone()));
}
self.native_gadget.linear_combination(layouter, &terms, constant * shift)
})
.collect()
}
fn index_and_pack_calls(
&self,
layouter: &mut impl Layouter<F>,
) -> Result<SubstringCheckLayout<F>, Error> {
let ng = &self.native_gadget;
let mut calls: Vec<_> = self.sequence_cache.borrow_mut().drain().collect();
calls.sort_by(|a, b| b.0.len().cmp(&a.0.len()).then(b.1 .1.cmp(&a.1 .1)));
let sequence_padding: AssignedNative<F> =
ng.assign_fixed(layouter, F::from(ALPHABET_MAX_SIZE as u64))?;
let zero: AssignedNative<F> = ng.assign_fixed(layouter, F::ZERO)?;
let mut layout: SubstringCheckLayout<F> =
Vec::with_capacity(calls.len().div_ceil(SUBSTRING_PARALLELISM));
for chunk in calls.chunks(SUBSTRING_PARALLELISM) {
let mut local_layout = Vec::with_capacity(NB_SUBSTRING_COLS * SUBSTRING_PARALLELISM);
let region_size = chunk.iter().map(|(s, (_, len))| s.len().max(*len)).max().unwrap();
let padded_size = region_size + 1;
for (sequence, (subs, _)) in chunk {
let mut padded_sequence: Sequence<F> = Vec::with_capacity(padded_size);
padded_sequence.push(zero.clone());
padded_sequence.extend_from_slice(sequence);
padded_sequence.resize(padded_size, sequence_padding.clone());
let subs_packed: Sequence<F> = (subs.iter())
.map(|(idx, idx_offsets, sub, padding_flags)| {
let packed =
self.index_and_pack_sequence(layouter, sub, idx, idx_offsets, 1)?;
if let Some(flags) = padding_flags {
packed
.iter()
.zip(flags.iter())
.map(|(val, is_pad)| ng.select(layouter, is_pad, &zero, val))
.collect()
} else {
Ok(packed)
}
})
.collect::<Result<Vec<Sequence<F>>, _>>()?
.into_iter()
.flatten()
.collect();
let mut padded_subs_packed: Sequence<F> = Vec::with_capacity(padded_size);
padded_subs_packed.push(zero.clone());
padded_subs_packed.extend(subs_packed);
padded_subs_packed.resize(padded_size, zero.clone());
local_layout.extend_from_slice(&[padded_sequence, padded_subs_packed]);
}
let zero_col = vec![zero.clone(); padded_size];
local_layout.resize(NB_SUBSTRING_COLS * SUBSTRING_PARALLELISM, zero_col);
layout.push(local_layout.try_into().unwrap());
}
Ok(layout)
}
fn assign_substring_row(
&self,
region: &mut Region<'_, F>,
lookups: &[AssignedNative<F>; NB_SUBSTRING_COLS * SUBSTRING_PARALLELISM],
offset: usize,
index: usize,
tag: usize,
) -> Result<(), Error> {
self.config.q_substring.enable(region, offset)?;
region.assign_fixed(
|| "substring check (index)",
self.config.index_col,
offset,
|| Value::known(F::from(index as u64)),
)?;
region.assign_fixed(
|| "substring check (tag)",
self.config.tag_col,
offset,
|| Value::known(F::from(tag as u64)),
)?;
for (i, cell) in lookups.iter().enumerate() {
cell.copy_advice(
|| {
format!(
"substring check ({} #{offset})",
if i.is_multiple_of(2) {
"table"
} else {
"query"
}
)
},
region,
self.config.advice_cols[i],
offset,
)?;
}
Ok(())
}
pub(super) fn finalise_substring_checks(
&self,
layouter: &mut impl Layouter<F>,
) -> Result<(), Error> {
let packed_calls = self.index_and_pack_calls(layouter)?;
layouter.assign_region(
|| "substring checks",
|mut region| {
let mut offset = 1;
for (tag, parallel_calls) in packed_calls.iter().enumerate() {
for row in 0..parallel_calls[0].len() {
let lookups = core::array::from_fn(|col| parallel_calls[col][row].clone());
self.assign_substring_row(&mut region, &lookups, offset, row, tag + 1)?;
offset += 1;
}
}
Ok(())
},
)
}
}
#[cfg(test)]
mod test {
use ff::FromUniformBytes;
use midnight_proofs::{
circuit::{Layouter, SimpleFloorPlanner, Value},
dev::MockProver,
plonk::{Circuit, ConstraintSystem, Error},
};
use super::super::ScannerChip;
use crate::{
instructions::AssignmentInstructions, testing_utils::FromScratch, types::AssignedByte,
utils::circuit_modeling::circuit_to_json, CircuitField,
};
#[derive(Clone, Debug)]
struct CheckBytesTestCircuit<F: CircuitField> {
full1: Vec<Value<u8>>,
sub1: Vec<Value<u8>>,
idx1: Value<F>,
full2: Vec<Value<u8>>,
sub2: Vec<Value<u8>>,
idx2: Value<F>,
}
impl<F: CircuitField> CheckBytesTestCircuit<F> {
fn new(case1: (&str, &str, usize), case2: (&str, &str, usize)) -> Self {
let (full1, sub1, idx1) = case1;
let (full2, sub2, idx2) = case2;
CheckBytesTestCircuit {
full1: full1.bytes().map(Value::known).collect(),
sub1: sub1.bytes().map(Value::known).collect(),
idx1: Value::known(F::from(idx1 as u64)),
full2: full2.bytes().map(Value::known).collect(),
sub2: sub2.bytes().map(Value::known).collect(),
idx2: Value::known(F::from(idx2 as u64)),
}
}
}
impl<F> Circuit<F> for CheckBytesTestCircuit<F>
where
F: CircuitField + FromUniformBytes<64> + Ord,
{
type Config = <ScannerChip<F> as FromScratch<F>>::Config;
type FloorPlanner = SimpleFloorPlanner;
type Params = ();
fn without_witnesses(&self) -> Self {
unreachable!()
}
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
let instance_columns = [meta.instance_column(), meta.instance_column()];
ScannerChip::<F>::configure_from_scratch(
meta,
&mut vec![],
&mut vec![],
&instance_columns,
)
}
fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<F>,
) -> Result<(), Error> {
let scanner = ScannerChip::<F>::new_from_scratch(&config);
let native_gadget = &scanner.native_gadget;
let full1: Vec<AssignedByte<F>> =
native_gadget.assign_many(&mut layouter, &self.full1)?;
let sub1: Vec<AssignedByte<F>> =
native_gadget.assign_many(&mut layouter, &self.sub1)?;
let idx1 = native_gadget.assign(&mut layouter, self.idx1)?;
let full2: Vec<AssignedByte<F>> =
native_gadget.assign_many(&mut layouter, &self.full2)?;
let sub2: Vec<AssignedByte<F>> =
native_gadget.assign_many(&mut layouter, &self.sub2)?;
let idx2 = native_gadget.assign(&mut layouter, self.idx2)?;
scanner.check_bytes(&mut layouter, &full1, &idx1, &sub1)?;
scanner.check_bytes(&mut layouter, &full2, &idx2, &sub2)?;
scanner.load_from_scratch(&mut layouter)
}
}
fn check_bytes_test(
cost_model: bool,
case1: (&str, &str, usize),
case2: (&str, &str, usize),
must_pass: bool,
) {
assert!(
!cost_model || must_pass,
"(bug) if cost_model is set to true, must_pass should be set to true"
);
type F = midnight_curves::Fq;
let circuit = CheckBytesTestCircuit::<F>::new(case1, case2);
println!(
">> [test check_bytes] [must{} pass] on\n\t- input1: \"{}\" = \"{}\"[{}..{}]\n\t- input2: \"{}\" = \"{}\"[{}..{}]",
if must_pass { "" } else { " not" },
case1.1,
case1.0,
case1.2,
case1.2 + case1.1.len(),
case2.1,
case2.0,
case2.2,
case2.2 + case2.1.len(),
);
let result = MockProver::run(&circuit, vec![vec![], vec![]]);
match result {
Ok(p) => {
let verified = p.verify();
if must_pass {
verified.expect("the test should have passed")
} else {
assert!(verified.is_err(), "the test should have failed");
}
}
Err(e) => {
assert!(!must_pass, "Prover failed unexpectedly: {:?}", e);
}
}
println!("... test successful!");
if cost_model {
circuit_to_json::<F>(
"Scanner",
&format!(
"substring perf (full length = {}, sub length = {})",
case1.0.len(),
case1.1.len()
),
circuit,
);
}
}
#[test]
fn test_check_bytes() {
let trivial = ("", "", 0);
check_bytes_test(false, trivial, trivial, true);
check_bytes_test(false, ("hello world", "hello", 0), trivial, true); check_bytes_test(false, ("hello world", "lo wo", 3), trivial, true); check_bytes_test(false, ("hello world", "world", 6), trivial, true); check_bytes_test(false, ("abcdef", "d", 3), trivial, true); check_bytes_test(false, ("test", "test", 0), trivial, true); check_bytes_test(false, ("hello world", "xyz", 0), trivial, false); check_bytes_test(false, ("hello world", "world", 0), trivial, false);
check_bytes_test(false, ("a", "b", 0), ("b", "a", 0), false);
check_bytes_test(
false,
("hello world", "hello", 0),
("world", " world", 1),
false,
);
check_bytes_test(false, ("hello", "ell", 1), ("world", "orl", 1), true);
let full = "abcdefghij abcde";
let sub = &full[6..11]; check_bytes_test(true, (full, sub, 6), ("world", "orl", 1), true);
}
}