use alloc::vec::Vec;
use core::cmp;
use fri::FriOptions;
use math::{FieldElement, StarkField, ToElements};
use utils::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable};
const MAX_NUM_QUERIES: usize = 255;
const MIN_BLOWUP_FACTOR: usize = 2;
const MAX_BLOWUP_FACTOR: usize = 128;
const MAX_GRINDING_FACTOR: u32 = 32;
const FRI_MIN_FOLDING_FACTOR: usize = 2;
const FRI_MAX_FOLDING_FACTOR: usize = 16;
const FRI_MAX_REMAINDER_DEGREE: usize = 255;
#[repr(u8)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum FieldExtension {
None = 1,
Quadratic = 2,
Cubic = 3,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ProofOptions {
num_queries: u8,
blowup_factor: u8,
grinding_factor: u8,
field_extension: FieldExtension,
fri_folding_factor: u8,
fri_remainder_max_degree: u8,
batching_constraints: BatchingMethod,
batching_deep: BatchingMethod,
partition_options: PartitionOptions,
}
impl ProofOptions {
pub const MIN_BLOWUP_FACTOR: usize = MIN_BLOWUP_FACTOR;
#[allow(clippy::too_many_arguments)]
pub const fn new(
num_queries: usize,
blowup_factor: usize,
grinding_factor: u32,
field_extension: FieldExtension,
fri_folding_factor: usize,
fri_remainder_max_degree: usize,
batching_constraints: BatchingMethod,
batching_deep: BatchingMethod,
) -> ProofOptions {
assert!(num_queries > 0, "number of queries must be greater than 0");
assert!(num_queries <= MAX_NUM_QUERIES, "number of queries cannot be greater than 255");
assert!(blowup_factor.is_power_of_two(), "blowup factor must be a power of 2");
assert!(blowup_factor >= MIN_BLOWUP_FACTOR, "blowup factor cannot be smaller than 2");
assert!(blowup_factor <= MAX_BLOWUP_FACTOR, "blowup factor cannot be greater than 128");
assert!(
grinding_factor <= MAX_GRINDING_FACTOR,
"grinding factor cannot be greater than 32"
);
assert!(fri_folding_factor.is_power_of_two(), "FRI folding factor must be a power of 2");
assert!(
fri_folding_factor >= FRI_MIN_FOLDING_FACTOR,
"FRI folding factor cannot be smaller than 2"
);
assert!(
fri_folding_factor <= FRI_MAX_FOLDING_FACTOR,
"FRI folding factor cannot be greater than 16"
);
assert!(
(fri_remainder_max_degree + 1).is_power_of_two(),
"FRI polynomial remainder degree must be one less than a power of two"
);
assert!(
fri_remainder_max_degree <= FRI_MAX_REMAINDER_DEGREE,
"FRI polynomial remainder degree cannot be greater than 255"
);
Self {
num_queries: num_queries as u8,
blowup_factor: blowup_factor as u8,
grinding_factor: grinding_factor as u8,
field_extension,
fri_folding_factor: fri_folding_factor as u8,
fri_remainder_max_degree: fri_remainder_max_degree as u8,
partition_options: PartitionOptions::new(1, 1),
batching_constraints,
batching_deep,
}
}
pub const fn with_partitions(
mut self,
num_partitions: usize,
hash_rate: usize,
) -> ProofOptions {
self.partition_options = PartitionOptions::new(num_partitions, hash_rate);
self
}
pub const fn num_queries(&self) -> usize {
self.num_queries as usize
}
pub const fn blowup_factor(&self) -> usize {
self.blowup_factor as usize
}
pub const fn grinding_factor(&self) -> u32 {
self.grinding_factor as u32
}
pub const fn field_extension(&self) -> FieldExtension {
self.field_extension
}
pub const fn domain_offset<B: StarkField>(&self) -> B {
B::GENERATOR
}
pub fn to_fri_options(&self) -> FriOptions {
let folding_factor = self.fri_folding_factor as usize;
let remainder_max_degree = self.fri_remainder_max_degree as usize;
FriOptions::new(self.blowup_factor(), folding_factor, remainder_max_degree)
}
pub fn partition_options(&self) -> PartitionOptions {
self.partition_options
}
pub fn constraint_batching_method(&self) -> BatchingMethod {
self.batching_constraints
}
pub fn deep_poly_batching_method(&self) -> BatchingMethod {
self.batching_deep
}
}
impl<E: StarkField> ToElements<E> for ProofOptions {
fn to_elements(&self) -> Vec<E> {
let mut buf = self.field_extension as u32;
buf = (buf << 8) | self.fri_folding_factor as u32;
buf = (buf << 8) | self.fri_remainder_max_degree as u32;
buf = (buf << 8) | self.blowup_factor as u32;
vec![E::from(buf), E::from(self.grinding_factor), E::from(self.num_queries)]
}
}
impl Serializable for ProofOptions {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
target.write_u8(self.num_queries);
target.write_u8(self.blowup_factor);
target.write_u8(self.grinding_factor);
target.write(self.field_extension);
target.write_u8(self.fri_folding_factor);
target.write_u8(self.fri_remainder_max_degree);
target.write(self.batching_constraints);
target.write(self.batching_deep);
target.write_u8(self.partition_options.num_partitions);
target.write_u8(self.partition_options.hash_rate);
}
}
impl Deserializable for ProofOptions {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let result = ProofOptions::new(
source.read_u8()? as usize,
source.read_u8()? as usize,
source.read_u8()? as u32,
FieldExtension::read_from(source)?,
source.read_u8()? as usize,
source.read_u8()? as usize,
BatchingMethod::read_from(source)?,
BatchingMethod::read_from(source)?,
);
Ok(result.with_partitions(source.read_u8()? as usize, source.read_u8()? as usize))
}
}
impl FieldExtension {
pub const fn is_none(&self) -> bool {
matches!(self, Self::None)
}
pub const fn degree(&self) -> u32 {
match self {
Self::None => 1,
Self::Quadratic => 2,
Self::Cubic => 3,
}
}
}
impl Serializable for FieldExtension {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
target.write_u8(*self as u8);
}
fn get_size_hint(&self) -> usize {
1
}
}
impl Deserializable for FieldExtension {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
match source.read_u8()? {
1 => Ok(FieldExtension::None),
2 => Ok(FieldExtension::Quadratic),
3 => Ok(FieldExtension::Cubic),
value => Err(DeserializationError::InvalidValue(format!(
"value {value} cannot be deserialized as FieldExtension enum"
))),
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct PartitionOptions {
num_partitions: u8,
hash_rate: u8,
}
impl PartitionOptions {
pub const fn new(num_partitions: usize, hash_rate: usize) -> Self {
assert!(num_partitions >= 1, "number of partitions must be greater than or equal to 1");
assert!(num_partitions <= 16, "number of partitions must be smaller than or equal to 16");
assert!(hash_rate >= 1, "hash rate must be greater than or equal to 1");
assert!(hash_rate <= 256, "hash rate must be smaller than or equal to 256");
Self {
num_partitions: num_partitions as u8,
hash_rate: hash_rate as u8,
}
}
pub fn partition_size<E: FieldElement>(&self, num_columns: usize) -> usize {
if self.num_partitions == 1 {
return num_columns;
}
let min_partition_size = self.hash_rate as usize / E::EXTENSION_DEGREE;
cmp::max(num_columns.div_ceil(self.num_partitions as usize), min_partition_size)
}
pub fn num_partitions<E: FieldElement>(&self, num_columns: usize) -> usize {
num_columns.div_ceil(self.partition_size::<E>(num_columns))
}
}
impl Default for PartitionOptions {
fn default() -> Self {
Self { num_partitions: 1, hash_rate: 1 }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum BatchingMethod {
Linear = 0,
Algebraic = 1,
Horner = 2,
}
impl Serializable for BatchingMethod {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
target.write_u8(*self as u8);
}
}
impl Deserializable for BatchingMethod {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
match source.read_u8()? {
0 => Ok(BatchingMethod::Linear),
1 => Ok(BatchingMethod::Algebraic),
2 => Ok(BatchingMethod::Horner),
n => Err(DeserializationError::InvalidValue(format!(
"value {n} cannot be deserialized as a BatchingMethod enum"
))),
}
}
}
#[cfg(test)]
mod tests {
use math::fields::{f64::BaseElement, CubeExtension};
use utils::{Deserializable, Serializable};
use super::{FieldExtension, PartitionOptions, ProofOptions, ToElements};
use crate::options::BatchingMethod;
#[test]
fn proof_options_to_elements() {
let field_extension = FieldExtension::None;
let fri_folding_factor = 8;
let fri_remainder_max_degree = 127;
let grinding_factor = 20;
let blowup_factor = 8;
let num_queries = 30;
let ext_fri = u32::from_le_bytes([
blowup_factor as u8,
fri_remainder_max_degree,
fri_folding_factor,
field_extension as u8,
]);
let expected = vec![
BaseElement::from(ext_fri),
BaseElement::from(grinding_factor),
BaseElement::from(num_queries as u32),
];
let options = ProofOptions::new(
num_queries,
blowup_factor,
grinding_factor,
field_extension,
fri_folding_factor as usize,
fri_remainder_max_degree as usize,
BatchingMethod::Linear,
BatchingMethod::Linear,
);
assert_eq!(expected, options.to_elements());
}
#[test]
fn correct_partition_sizes() {
type E1 = BaseElement;
type E3 = CubeExtension<BaseElement>;
let options = PartitionOptions::new(4, 8);
let columns = 7;
assert_eq!(8, options.partition_size::<E1>(columns));
assert_eq!(1, options.num_partitions::<E1>(columns));
let options = PartitionOptions::new(4, 8);
let columns = 70;
assert_eq!(18, options.partition_size::<E1>(columns));
assert_eq!(4, options.num_partitions::<E1>(columns));
let options = PartitionOptions::new(2, 8);
let columns = 7;
assert_eq!(4, options.partition_size::<E3>(columns));
assert_eq!(2, options.num_partitions::<E3>(columns));
let options: PartitionOptions = PartitionOptions::new(4, 8);
let columns = 7;
assert_eq!(2, options.partition_size::<E3>(columns));
assert_eq!(4, options.num_partitions::<E3>(columns));
let options: PartitionOptions = PartitionOptions::new(4, 8);
let columns = 3;
assert_eq!(2, options.partition_size::<E3>(columns));
assert_eq!(2, options.num_partitions::<E3>(columns));
}
#[test]
fn serialization_proof_options() {
let field_extension = FieldExtension::Quadratic;
let fri_folding_factor = 8;
let fri_remainder_max_degree = 127;
let grinding_factor = 20;
let blowup_factor = 8;
let num_queries = 30;
let options = ProofOptions::new(
num_queries,
blowup_factor,
grinding_factor,
field_extension,
fri_folding_factor as usize,
fri_remainder_max_degree as usize,
BatchingMethod::Linear,
BatchingMethod::Horner,
);
let options_serialized = options.to_bytes();
let options_deserialized = ProofOptions::read_from_bytes(&options_serialized).unwrap();
assert_eq!(options, options_deserialized)
}
}