diff --git a/crates/proof-of-sql/src/proof_primitive/dory/dory_compute_commitments_test.rs b/crates/proof-of-sql/src/proof_primitive/dory/dory_compute_commitments_test.rs index d2b09559b..7e15110c7 100644 --- a/crates/proof-of-sql/src/proof_primitive/dory/dory_compute_commitments_test.rs +++ b/crates/proof-of-sql/src/proof_primitive/dory/dory_compute_commitments_test.rs @@ -328,6 +328,36 @@ fn we_can_compute_an_empty_dory_commitment() { assert_eq!(res[0].0, GT::zero()); let res = compute_dory_commitments(&[CommittableColumn::BigInt(&[0; 0])], 20, &setup); assert_eq!(res[0].0, GT::zero()); + let res = compute_dory_commitments( + &[ + CommittableColumn::BigInt(&[0; 0]), + CommittableColumn::BigInt(&[0; 0]), + ], + 0, + &setup, + ); + assert_eq!(res[0].0, GT::zero()); + assert_eq!(res[1].0, GT::zero()); + let res = compute_dory_commitments( + &[ + CommittableColumn::BigInt(&[0; 0]), + CommittableColumn::BigInt(&[0; 0]), + ], + 5, + &setup, + ); + assert_eq!(res[0].0, GT::zero()); + assert_eq!(res[1].0, GT::zero()); + let res = compute_dory_commitments( + &[ + CommittableColumn::BigInt(&[0; 0]), + CommittableColumn::BigInt(&[0; 0]), + ], + 20, + &setup, + ); + assert_eq!(res[0].0, GT::zero()); + assert_eq!(res[1].0, GT::zero()); } #[test] diff --git a/crates/proof-of-sql/src/proof_primitive/dory/dynamic_dory_commitment_helper_gpu.rs b/crates/proof-of-sql/src/proof_primitive/dory/dynamic_dory_commitment_helper_gpu.rs new file mode 100644 index 000000000..f6aa6fba2 --- /dev/null +++ b/crates/proof-of-sql/src/proof_primitive/dory/dynamic_dory_commitment_helper_gpu.rs @@ -0,0 +1,418 @@ +use super::{ + dynamic_dory_structure::{full_width_of_row, index_from_row_and_column, matrix_size}, + pairings, DynamicDoryCommitment, G1Affine, ProverSetup, +}; +use crate::{ + base::{commitment::CommittableColumn, slice_ops::slice_cast}, + proof_primitive::dory::{offset_to_bytes::OffsetToBytes, pack_scalars::min_as_f}, +}; +use ark_ec::CurveGroup; +use ark_std::ops::Mul; +use blitzar::compute::ElementP2; +use core::iter; +use itertools::Itertools; +use tracing::{span, Level}; + +const BYTE_SIZE: u32 = 8; + +/// Modifies the sub commits by adding the minimum commitment of the column type to the signed sub commits. +/// +/// # Arguments +/// +/// * `all_sub_commits` - A reference to the sub commits. +/// * `committable_columns` - A reference to the committable columns. +/// +/// # Returns +/// +/// A vector containing the modified sub commits to be used by the dynamic Dory commitment computation. +#[tracing::instrument(name = "signed_commits", level = "debug", skip_all)] +fn signed_commits( + all_sub_commits: &Vec, + committable_columns: &[CommittableColumn], +) -> Vec { + let mut unsigned_sub_commits: Vec = Vec::new(); + let mut min_sub_commits: Vec = Vec::new(); + let mut counter = 0; + + // Every sub_commit has a corresponding offset sub_commit committable_columns.len() away. + // The commits and respective ones commits are interleaved in the all_sub_commits vector. + for commit in all_sub_commits { + if counter < committable_columns.len() { + unsigned_sub_commits.push(*commit); + } else { + let min = + min_as_f(committable_columns[counter - committable_columns.len()].column_type()); + min_sub_commits.push(commit.mul(min).into_affine()); + } + counter += 1; + if counter == 2 * committable_columns.len() { + counter = 0; + } + } + + unsigned_sub_commits + .into_iter() + .zip(min_sub_commits.into_iter()) + .map(|(unsigned, min)| (unsigned + min).into()) + .collect() +} + +/// Copies the column data to the scalar row slice. +/// +/// # Arguments +/// +/// * `column` - A reference to the committable column. +/// * `scalar_row_slice` - A mutable reference to the scalar row slice. +/// * `start` - The start index of the slice. +/// * `end` - The end index of the slice. +/// * `index` - The index of the column. +fn copy_column_data_to_slice( + column: &CommittableColumn, + scalar_row_slice: &mut [u8], + start: usize, + end: usize, + index: usize, +) { + match column { + CommittableColumn::Boolean(column) => { + scalar_row_slice[start..end].copy_from_slice(&column[index].offset_to_bytes()); + } + CommittableColumn::TinyInt(column) => { + scalar_row_slice[start..end].copy_from_slice(&column[index].offset_to_bytes()); + } + CommittableColumn::SmallInt(column) => { + scalar_row_slice[start..end].copy_from_slice(&column[index].offset_to_bytes()); + } + CommittableColumn::Int(column) => { + scalar_row_slice[start..end].copy_from_slice(&column[index].offset_to_bytes()); + } + CommittableColumn::BigInt(column) | CommittableColumn::TimestampTZ(_, _, column) => { + scalar_row_slice[start..end].copy_from_slice(&column[index].offset_to_bytes()); + } + CommittableColumn::Int128(column) => { + scalar_row_slice[start..end].copy_from_slice(&column[index].offset_to_bytes()); + } + CommittableColumn::Scalar(column) + | CommittableColumn::Decimal75(_, _, column) + | CommittableColumn::VarChar(column) => { + scalar_row_slice[start..end].copy_from_slice(&column[index].offset_to_bytes()); + } + CommittableColumn::RangeCheckWord(_) => todo!(), + } +} + +/// Creates the metadata tables for Blitzar's `vlen_msm` algorithm. +/// +/// # Arguments +/// +/// * `committable_columns` - A reference to the committable columns. +/// * `offset` - The offset to the data. +/// +/// # Returns +/// +/// A tuple containing the output bit table, output length table, +/// and scalars required to call Blitzar's `vlen_msm` function. +#[tracing::instrument(name = "create_blitzar_metadata_tables", level = "debug", skip_all)] +fn create_blitzar_metadata_tables( + committable_columns: &[CommittableColumn], + offset: usize, +) -> (Vec, Vec, Vec) { + // Keep track of the lengths of the columns to handled signed data columns. + let ones_columns_lengths = committable_columns + .iter() + .map(CommittableColumn::len) + .collect_vec(); + + // The maximum matrix size will be used to create the scalars vector. + let (max_height, max_width) = if let Some(max_column_len) = + committable_columns.iter().map(CommittableColumn::len).max() + { + matrix_size(max_column_len, offset) + } else { + (0, 0) + }; + + // Find the single packed byte size of all committable columns. + let num_of_bytes_in_committable_columns: usize = committable_columns + .iter() + .map(|column| column.column_type().byte_size()) + .sum(); + + // Get a single bit table entry with ones added for all committable columns that are signed. + let single_entry_in_blitzar_output_bit_table: Vec = committable_columns + .iter() + .map(|column| column.column_type().bit_size()) + .chain(iter::repeat(BYTE_SIZE).take(ones_columns_lengths.len())) + .collect(); + + // Create the full bit table vector to be used by Blitzar's vlen_msm algorithm. + let blitzar_output_bit_table: Vec = single_entry_in_blitzar_output_bit_table + .iter() + .copied() + .cycle() + .take(single_entry_in_blitzar_output_bit_table.len() * max_height) + .collect(); + + // Create the full length vector to be used by Blitzar's vlen_msm algorithm. + let blitzar_output_length_table: Vec = (0..blitzar_output_bit_table.len() + / single_entry_in_blitzar_output_bit_table.len()) + .flat_map(|i| { + itertools::repeat_n( + full_width_of_row(i) as u32, + single_entry_in_blitzar_output_bit_table.len(), + ) + }) + .collect(); + + // Create a cumulative length table to be used when packing the scalar vector. + let cumulative_byte_length_table: Vec = iter::once(0) + .chain(blitzar_output_bit_table.iter().scan(0usize, |acc, &x| { + *acc += (x / BYTE_SIZE) as usize; + Some(*acc) + })) + .collect(); + + // Create scalars array. Note, scalars need to be stored in a column-major order. + let num_scalar_rows = max_width; + let num_scalar_columns = + (num_of_bytes_in_committable_columns + ones_columns_lengths.len()) * max_height; + let mut blitzar_scalars = vec![0u8; num_scalar_rows * num_scalar_columns]; + + // Populate the scalars array. + let span = span!(Level::INFO, "pack_blitzar_scalars").entered(); + if !blitzar_scalars.is_empty() { + blitzar_scalars + .chunks_exact_mut(num_scalar_columns) + .enumerate() + .for_each(|(scalar_row, scalar_row_slice)| { + // Iterate over the columns and populate the scalars array. + for scalar_col in 0..max_height { + // Find index in the committable columns. Note, the scalar is in + // column major order, that is why the (row, col) arguments are flipped. + if let Some(index) = index_from_row_and_column(scalar_col, scalar_row).and_then( + |committable_column_idx| committable_column_idx.checked_sub(offset), + ) { + for (i, committable_column) in committable_columns + .iter() + .enumerate() + .filter(|(_, committable_column)| index < committable_column.len()) + { + let start = cumulative_byte_length_table + [i + scalar_col * single_entry_in_blitzar_output_bit_table.len()]; + let end = start + + (single_entry_in_blitzar_output_bit_table[i] / BYTE_SIZE) + as usize; + + copy_column_data_to_slice( + committable_column, + scalar_row_slice, + start, + end, + index, + ); + } + + ones_columns_lengths + .iter() + .positions(|ones_columns_length| index < *ones_columns_length) + .for_each(|i| { + let ones_index = i + + scalar_col + * (num_of_bytes_in_committable_columns + + ones_columns_lengths.len()) + + num_of_bytes_in_committable_columns; + + scalar_row_slice[ones_index] = 1_u8; + }); + } + } + }); + } + span.exit(); + + ( + blitzar_output_bit_table, + blitzar_output_length_table, + blitzar_scalars, + ) +} + +/// Computes the dynamic Dory commitment using the GPU implementation of the `vlen_msm` algorithm. +/// +/// # Arguments +/// +/// * `committable_columns` - A reference to the committable columns. +/// * `offset` - The offset to the data. +/// * `setup` - A reference to the prover setup. +/// +/// # Returns +/// +/// A vector containing the dynamic Dory commitments. +/// +/// # Panics +/// +/// Panics if the number of sub commits is not a multiple of the number of committable columns. +#[tracing::instrument( + name = "compute_dynamic_dory_commitments (gpu)", + level = "debug", + skip_all +)] +pub(super) fn compute_dynamic_dory_commitments( + committable_columns: &[CommittableColumn], + offset: usize, + setup: &ProverSetup, +) -> Vec { + let Gamma_2 = setup.Gamma_2.last().unwrap(); + + // Get metadata tables for Blitzar's vlen_msm algorithm. + let (blitzar_output_bit_table, blitzar_output_length_table, blitzar_scalars) = + create_blitzar_metadata_tables(committable_columns, offset); + + // Initialize sub commits. + let mut blitzar_sub_commits = + vec![ElementP2::::default(); blitzar_output_bit_table.len()]; + + // Get sub commits from Blitzar's vlen_msm algorithm. + setup.blitzar_vlen_msm( + &mut blitzar_sub_commits, + &blitzar_output_bit_table, + &blitzar_output_length_table, + blitzar_scalars.as_slice(), + ); + + // Modify the sub commits to include the signed offset. + let all_sub_commits: Vec = slice_cast(&blitzar_sub_commits); + let signed_sub_commits = signed_commits(&all_sub_commits, committable_columns); + assert!( + signed_sub_commits.len() % committable_columns.len() == 0, + "Invalid number of sub commits" + ); + let num_commits = signed_sub_commits.len() / committable_columns.len(); + + // Calculate the dynamic Dory commitments. + let span = span!(Level::INFO, "multi_pairing").entered(); + let ddc: Vec = signed_sub_commits + .is_empty() + .then_some(vec![ + DynamicDoryCommitment::default(); + committable_columns.len() + ]) + .unwrap_or_else(|| { + (0..committable_columns.len()) + .map(|i| { + let sub_slice = signed_sub_commits[i..] + .iter() + .step_by(committable_columns.len()) + .take(num_commits); + DynamicDoryCommitment(pairings::multi_pairing( + sub_slice, + &Gamma_2[..num_commits], + )) + }) + .collect() + }); + span.exit(); + + ddc +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::base::math::decimal::Precision; + use proof_of_sql_parser::posql_time::{PoSQLTimeUnit, PoSQLTimeZone}; + + #[test] + fn we_can_populate_blitzar_metadata_tables_with_empty_columns() { + let committable_columns = [CommittableColumn::BigInt(&[0; 0])]; + let offset = 0; + let (bit_table, length_table, scalars) = + create_blitzar_metadata_tables(&committable_columns, offset); + + assert!(bit_table.is_empty()); + assert!(length_table.is_empty()); + assert!(scalars.is_empty()); + } + + #[test] + fn we_can_populate_blitzar_metadata_tables_with_empty_columns_and_an_offset() { + let committable_columns = [CommittableColumn::BigInt(&[0; 0])]; + let offset = 1; + let (bit_table, length_table, scalars) = + create_blitzar_metadata_tables(&committable_columns, offset); + + assert_eq!(bit_table, vec![64, 8]); + assert_eq!(length_table, vec![1, 1]); + assert_eq!(scalars, vec![0, 0, 0, 0, 0, 0, 0, 0, 0]); + } + + #[test] + fn we_can_populate_blitzar_metadata_tables_with_simple_column() { + let committable_columns = [CommittableColumn::BigInt(&[1])]; + let offset = 0; + let (bit_table, length_table, scalars) = + create_blitzar_metadata_tables(&committable_columns, offset); + + assert_eq!(bit_table, vec![64, 8]); + assert_eq!(length_table, vec![1, 1]); + assert_eq!(scalars, vec![1, 0, 0, 0, 0, 0, 0, 128, 1]); + } + + #[test] + fn we_can_populate_blitzar_metadata_tables_with_simple_column_and_offset() { + let committable_columns = [CommittableColumn::BigInt(&[1])]; + let offset = 1; + let (bit_table, length_table, scalars) = + create_blitzar_metadata_tables(&committable_columns, offset); + + assert_eq!(bit_table, vec![64, 8, 64, 8]); + assert_eq!(length_table, vec![1, 1, 2, 2]); + assert_eq!( + scalars, + vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 128, 1 + ] + ); + } + + #[test] + fn we_can_populate_blitzar_metadata_tables_with_mixed_columns() { + let committable_columns = [ + CommittableColumn::TinyInt(&[1]), + CommittableColumn::SmallInt(&[2]), + CommittableColumn::Int(&[3]), + CommittableColumn::BigInt(&[4]), + CommittableColumn::Int128(&[5]), + CommittableColumn::Decimal75(Precision::new(1).unwrap(), 0, vec![[6, 0, 0, 0]]), + CommittableColumn::Scalar(vec![[7, 0, 0, 0]]), + CommittableColumn::VarChar(vec![[8, 0, 0, 0]]), + CommittableColumn::TimestampTZ(PoSQLTimeUnit::Second, PoSQLTimeZone::Utc, &[9]), + CommittableColumn::Boolean(&[true]), + ]; + + let offset = 0; + let (bit_table, length_table, scalars) = + create_blitzar_metadata_tables(&committable_columns, offset); + assert_eq!( + bit_table, + vec![8, 16, 32, 64, 128, 256, 256, 256, 64, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8] + ); + + assert_eq!( + length_table, + vec![1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + ); + assert_eq!( + scalars, + vec![ + 129, 2, 128, 3, 0, 0, 128, 4, 0, 0, 0, 0, 0, 0, 128, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 128, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 128, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + ] + ); + } +} diff --git a/crates/proof-of-sql/src/proof_primitive/dory/dynamic_dory_compute_commitments_test.rs b/crates/proof-of-sql/src/proof_primitive/dory/dynamic_dory_compute_commitments_test.rs index b6426efba..20519a74c 100644 --- a/crates/proof-of-sql/src/proof_primitive/dory/dynamic_dory_compute_commitments_test.rs +++ b/crates/proof-of-sql/src/proof_primitive/dory/dynamic_dory_compute_commitments_test.rs @@ -185,6 +185,36 @@ fn we_can_compute_an_empty_dynamic_dory_commitment() { assert_eq!(res[0].0, GT::zero()); let res = compute_dynamic_dory_commitments(&[CommittableColumn::BigInt(&[0; 0])], 20, &setup); assert_eq!(res[0].0, GT::zero()); + let res = compute_dynamic_dory_commitments( + &[ + CommittableColumn::BigInt(&[0; 0]), + CommittableColumn::BigInt(&[0; 0]), + ], + 0, + &setup, + ); + assert_eq!(res[0].0, GT::zero()); + assert_eq!(res[1].0, GT::zero()); + let res = compute_dynamic_dory_commitments( + &[ + CommittableColumn::BigInt(&[0; 0]), + CommittableColumn::BigInt(&[0; 0]), + ], + 5, + &setup, + ); + assert_eq!(res[0].0, GT::zero()); + assert_eq!(res[1].0, GT::zero()); + let res = compute_dynamic_dory_commitments( + &[ + CommittableColumn::BigInt(&[0; 0]), + CommittableColumn::BigInt(&[0; 0]), + ], + 20, + &setup, + ); + assert_eq!(res[0].0, GT::zero()); + assert_eq!(res[1].0, GT::zero()); } #[test] diff --git a/crates/proof-of-sql/src/proof_primitive/dory/dynamic_dory_structure.rs b/crates/proof-of-sql/src/proof_primitive/dory/dynamic_dory_structure.rs index 2f4146060..2598db988 100644 --- a/crates/proof-of-sql/src/proof_primitive/dory/dynamic_dory_structure.rs +++ b/crates/proof-of-sql/src/proof_primitive/dory/dynamic_dory_structure.rs @@ -33,7 +33,6 @@ /// Returns the full width of a row in the matrix. /// /// Note: when row = 1, this correctly returns 2, even though no data belongs at position 0. -#[allow(dead_code)] pub(crate) const fn full_width_of_row(row: usize) -> usize { ((2 * row + 4) / 3).next_power_of_two() } @@ -56,7 +55,6 @@ pub(crate) const fn row_and_column_from_index(index: usize) -> (usize, usize) { } /// Returns the index of data where the (row, column) belongs. -#[allow(dead_code)] pub(crate) fn index_from_row_and_column(row: usize, column: usize) -> Option { let width_of_row = full_width_of_row(row); (column < width_of_row && (row, column) != (1, 0)) @@ -66,7 +64,6 @@ pub(crate) fn index_from_row_and_column(row: usize, column: usize) -> Option (usize, usize) { if data_len == 0 && offset == 0 { return (0, 0); diff --git a/crates/proof-of-sql/src/proof_primitive/dory/mod.rs b/crates/proof-of-sql/src/proof_primitive/dory/mod.rs index 08bbeb6c8..3c4070bac 100644 --- a/crates/proof-of-sql/src/proof_primitive/dory/mod.rs +++ b/crates/proof-of-sql/src/proof_primitive/dory/mod.rs @@ -144,11 +144,17 @@ mod pairings; mod transpose; mod dynamic_build_vmv_state; +#[cfg(not(feature = "blitzar"))] mod dynamic_dory_commitment_helper_cpu; +#[cfg(feature = "blitzar")] +mod dynamic_dory_commitment_helper_gpu; mod dynamic_dory_helper; mod dynamic_dory_standard_basis_helper; mod dynamic_dory_structure; +#[cfg(not(feature = "blitzar"))] use dynamic_dory_commitment_helper_cpu::compute_dynamic_dory_commitments; +#[cfg(feature = "blitzar")] +use dynamic_dory_commitment_helper_gpu::compute_dynamic_dory_commitments; mod dynamic_dory_commitment; mod dynamic_dory_commitment_evaluation_proof; #[cfg(test)] diff --git a/crates/proof-of-sql/src/proof_primitive/dory/pack_scalars.rs b/crates/proof-of-sql/src/proof_primitive/dory/pack_scalars.rs index e77410cb4..8797a315b 100644 --- a/crates/proof-of-sql/src/proof_primitive/dory/pack_scalars.rs +++ b/crates/proof-of-sql/src/proof_primitive/dory/pack_scalars.rs @@ -55,7 +55,7 @@ fn output_bit_table( /// # Arguments /// /// * `column_type` - The type of a committable column. -const fn min_as_f(column_type: ColumnType) -> F { +pub const fn min_as_f(column_type: ColumnType) -> F { match column_type { ColumnType::TinyInt => MontFp!("-128"), ColumnType::SmallInt => MontFp!("-32768"), diff --git a/crates/proof-of-sql/src/proof_primitive/dory/setup.rs b/crates/proof-of-sql/src/proof_primitive/dory/setup.rs index b06199e15..4283fa7d5 100644 --- a/crates/proof-of-sql/src/proof_primitive/dory/setup.rs +++ b/crates/proof-of-sql/src/proof_primitive/dory/setup.rs @@ -88,6 +88,19 @@ impl<'a> ProverSetup<'a> { self.blitzar_handle .packed_msm(res, output_bit_table, scalars); } + + #[cfg(feature = "blitzar")] + #[tracing::instrument(name = "ProverSetup::blitzar_vlen_msm", level = "debug", skip_all)] + pub(super) fn blitzar_vlen_msm( + &self, + res: &mut [blitzar::compute::ElementP2], + output_bit_table: &[u32], + output_lengths: &[u32], + scalars: &[u8], + ) { + self.blitzar_handle + .vlen_msm(res, output_bit_table, output_lengths, scalars); + } } impl<'a> From<&'a PublicParameters> for ProverSetup<'a> {