use alloc::vec::Vec;
use p3_field::Field;
use p3_maybe_rayon::prelude::*;
use p3_multilinear_util::poly::Poly;
use super::lde::CHUNK_BITS;
pub fn embed_bits<F: Field>(packed: &[u8]) -> Poly<F> {
assert!(
(packed.len() * CHUNK_BITS).is_power_of_two(),
"the cell count must be a power of two"
);
let values = packed
.par_iter()
.flat_map_iter(|&byte| {
(0..CHUNK_BITS).map(move |bit| {
if (byte >> bit) & 1 == 1 {
F::ONE
} else {
F::ZERO
}
})
})
.collect::<Vec<_>>();
Poly::new(values)
}
#[cfg(test)]
mod tests {
use p3_binary_field::BinaryField128;
use p3_field::PrimeCharacteristicRing;
use p3_multilinear_util::point::Point;
use rand::rngs::SmallRng;
use rand::{RngExt, SeedableRng};
use super::*;
use crate::univariate_skip::{Composition, Conjunction};
type EF = BinaryField128;
#[test]
fn the_embedding_reads_the_documented_bit_order() {
let packed = [0b0000_0010u8, 0];
let embedded = embed_bits::<EF>(&packed);
assert_eq!(embedded.num_evals(), 16);
for cell in 0..16 {
let expected = if cell == 1 { EF::ONE } else { EF::ZERO };
assert_eq!(embedded.as_slice()[cell], expected, "cell={cell}");
}
}
#[test]
fn the_second_byte_holds_the_next_eight_cells() {
let packed = [0u8, 0b1000_0000];
let embedded = embed_bits::<EF>(&packed);
assert_eq!(embedded.as_slice()[15], EF::ONE);
assert!(
embedded.as_slice()[..15]
.iter()
.all(|&value| value == EF::ZERO)
);
}
#[test]
fn this_embedding_puts_only_bits_in_the_cells() {
let mut rng = SmallRng::seed_from_u64(0xB175);
let packed = (0..32).map(|_| rng.random::<u8>()).collect::<Vec<_>>();
let embedded = embed_bits::<EF>(&packed);
assert!(
embedded
.as_slice()
.iter()
.all(|&value| value == EF::ZERO || value == EF::ONE)
);
}
#[test]
fn the_constraint_cannot_tell_a_bit_from_a_field_element() {
let mut rng = SmallRng::seed_from_u64(0xF1E1D);
for _ in 0..32 {
let (a, b) = (rng.random::<EF>(), rng.random::<EF>());
let c = a * b;
assert!(a != EF::ZERO && a != EF::ONE);
assert!(b != EF::ZERO && b != EF::ONE);
assert_eq!(Composition::<EF>::eval(&Conjunction, &[a, b, c]), EF::ZERO);
}
}
#[test]
fn the_embedding_is_the_multilinear_the_rows_describe() {
let mut rng = SmallRng::seed_from_u64(0x3B1);
let packed = (0..16).map(|_| rng.random::<u8>()).collect::<Vec<_>>();
let embedded = embed_bits::<EF>(&packed);
let point = Point::new((0..7).map(|_| rng.random::<EF>()).collect());
let eq = Poly::new_from_point(point.as_slice(), EF::ONE);
let expected = (0..packed.len() * CHUNK_BITS)
.filter(|&cell| (packed[cell / CHUNK_BITS] >> (cell % CHUNK_BITS)) & 1 == 1)
.map(|cell| eq.as_slice()[cell])
.sum::<EF>();
assert_eq!(embedded.eval_base(&point), expected);
}
#[test]
#[should_panic(expected = "cell count must be a power of two")]
fn rejects_a_witness_that_is_not_a_hypercube() {
let _ = embed_bits::<EF>(&[0u8; 3]);
}
}