use super::Commitment;
use crate::base::commitment::committable_column::CommittableColumn;
use alloc::vec::Vec;
use snafu::Snafu;
#[derive(Snafu, Debug)]
#[snafu(display("cannot update commitment collections with different column counts"))]
pub struct NumColumnsMismatch;
pub trait VecCommitmentExt {
type CommitmentPublicSetup<'a>;
fn from_columns_with_offset<'a, C>(
columns: impl IntoIterator<Item = C>,
offset: usize,
setup: &Self::CommitmentPublicSetup<'_>,
) -> Self
where
C: Into<CommittableColumn<'a>>;
fn from_commitable_columns_with_offset(
committable_columns: &[CommittableColumn],
offset: usize,
setup: &Self::CommitmentPublicSetup<'_>,
) -> Self;
fn try_append_rows_with_offset<'a, C>(
&mut self,
columns: impl IntoIterator<Item = C>,
offset: usize,
setup: &Self::CommitmentPublicSetup<'_>,
) -> Result<(), NumColumnsMismatch>
where
C: Into<CommittableColumn<'a>>;
fn extend_columns_with_offset<'a, C>(
&mut self,
columns: impl IntoIterator<Item = C>,
offset: usize,
setup: &Self::CommitmentPublicSetup<'_>,
) where
C: Into<CommittableColumn<'a>>;
fn try_add(self, other: Self) -> Result<Self, NumColumnsMismatch>
where
Self: Sized;
fn try_sub(self, other: Self) -> Result<Self, NumColumnsMismatch>
where
Self: Sized;
fn num_commitments(&self) -> usize;
}
fn unsafe_add_assign<C: Commitment>(a: &mut [C], b: &[C]) {
a.iter_mut().zip(b).for_each(|(c_a, c_b)| {
*c_a += c_b.clone();
});
}
fn unsafe_sub_assign<C: Commitment>(a: &mut [C], b: &[C]) {
a.iter_mut().zip(b).for_each(|(c_a, c_b)| {
*c_a -= c_b.clone();
});
}
impl<C: Commitment> VecCommitmentExt for Vec<C> {
type CommitmentPublicSetup<'a> = C::PublicSetup<'a>;
fn from_columns_with_offset<'a, COL>(
columns: impl IntoIterator<Item = COL>,
offset: usize,
setup: &Self::CommitmentPublicSetup<'_>,
) -> Self
where
COL: Into<CommittableColumn<'a>>,
{
let committable_columns: Vec<CommittableColumn<'a>> =
columns.into_iter().map(Into::into).collect::<Vec<_>>();
Self::from_commitable_columns_with_offset(&committable_columns, offset, setup)
}
fn from_commitable_columns_with_offset(
committable_columns: &[CommittableColumn],
offset: usize,
setup: &Self::CommitmentPublicSetup<'_>,
) -> Self {
C::compute_commitments(committable_columns, offset, setup)
}
fn try_append_rows_with_offset<'a, COL>(
&mut self,
columns: impl IntoIterator<Item = COL>,
offset: usize,
setup: &Self::CommitmentPublicSetup<'_>,
) -> Result<(), NumColumnsMismatch>
where
COL: Into<CommittableColumn<'a>>,
{
let committable_columns: Vec<CommittableColumn<'a>> =
columns.into_iter().map(Into::into).collect::<Vec<_>>();
if self.len() != committable_columns.len() {
return Err(NumColumnsMismatch);
}
let partial_commitments = C::compute_commitments(&committable_columns, offset, setup);
unsafe_add_assign(self, &partial_commitments);
Ok(())
}
fn extend_columns_with_offset<'a, COL>(
&mut self,
columns: impl IntoIterator<Item = COL>,
offset: usize,
setup: &Self::CommitmentPublicSetup<'_>,
) where
COL: Into<CommittableColumn<'a>>,
{
self.extend(Self::from_columns_with_offset(columns, offset, setup))
}
fn try_add(self, other: Self) -> Result<Self, NumColumnsMismatch>
where
Self: Sized,
{
if self.len() != other.len() {
return Err(NumColumnsMismatch);
}
let mut commitments = self;
unsafe_add_assign(&mut commitments, &other);
Ok(commitments)
}
fn try_sub(self, other: Self) -> Result<Self, NumColumnsMismatch>
where
Self: Sized,
{
if self.len() != other.len() {
return Err(NumColumnsMismatch);
}
let mut commitments = self;
unsafe_sub_assign(&mut commitments, &other);
Ok(commitments)
}
fn num_commitments(&self) -> usize {
self.len()
}
}
#[cfg(all(test, feature = "blitzar"))]
mod tests {
use super::*;
use crate::base::{
database::{Column, OwnedColumn},
scalar::Curve25519Scalar,
};
use blitzar::{compute::compute_curve25519_commitments, sequence::Sequence};
use curve25519_dalek::{ristretto::CompressedRistretto, RistrettoPoint};
#[test]
fn we_can_convert_from_columns() {
let commitments = Vec::<RistrettoPoint>::from_columns_with_offset(
&Vec::<Column<Curve25519Scalar>>::new(),
0,
&(),
);
assert!(commitments.is_empty());
let column_a = [12i64, 34, 56];
let column_b = ["Lorem", "ipsum", "dolor"].map(String::from);
let columns = vec![
OwnedColumn::<Curve25519Scalar>::BigInt(column_a.to_vec()),
OwnedColumn::VarChar(column_b.to_vec()),
];
let commitments = Vec::<RistrettoPoint>::from_columns_with_offset(&columns, 0, &());
let mut expected_commitments = vec![CompressedRistretto::default(); 2];
compute_curve25519_commitments(
&mut expected_commitments,
&[
Sequence::from(column_a.as_slice()),
Sequence::from(
column_b
.map(Curve25519Scalar::from)
.map(<[u64; 4]>::from)
.as_slice(),
),
],
0,
);
let expected_commitments =
Vec::from_iter(expected_commitments.iter().map(|c| c.decompress().unwrap()));
assert_eq!(commitments, expected_commitments);
}
#[test]
fn we_can_append_rows() {
let column_a = [12i64, 34, 56, 78, 90];
let column_b = ["Lorem", "ipsum", "dolor", "sit", "amet"].map(String::from);
let columns = vec![
OwnedColumn::<Curve25519Scalar>::BigInt(column_a[..3].to_vec()),
OwnedColumn::VarChar(column_b[..3].to_vec()),
];
let mut commitments = Vec::<RistrettoPoint>::from_columns_with_offset(&columns, 0, &());
let new_columns = vec![
OwnedColumn::<Curve25519Scalar>::BigInt(column_a[3..].to_vec()),
OwnedColumn::VarChar(column_b[3..].to_vec()),
];
commitments
.try_append_rows_with_offset(&new_columns, 3, &())
.unwrap();
let mut expected_commitments = vec![CompressedRistretto::default(); 2];
compute_curve25519_commitments(
&mut expected_commitments,
&[
Sequence::from(column_a.as_slice()),
Sequence::from(
column_b
.map(Curve25519Scalar::from)
.map(<[u64; 4]>::from)
.as_slice(),
),
],
0,
);
let expected_commitments =
Vec::from_iter(expected_commitments.iter().map(|c| c.decompress().unwrap()));
assert_eq!(commitments, expected_commitments);
}
#[test]
fn we_cannot_append_rows_with_different_column_count() {
let column_a = [12i64, 34, 56, 78, 90];
let column_b = ["Lorem", "ipsum", "dolor", "sit", "amet"].map(String::from);
let columns = vec![
OwnedColumn::<Curve25519Scalar>::BigInt(column_a[..3].to_vec()),
OwnedColumn::VarChar(column_b[..3].to_vec()),
];
let mut commitments = Vec::<RistrettoPoint>::from_columns_with_offset(&columns, 0, &());
let new_columns = Vec::<Column<Curve25519Scalar>>::new();
assert!(matches!(
commitments.try_append_rows_with_offset(&new_columns, 3, &()),
Err(NumColumnsMismatch)
));
let new_columns = vec![OwnedColumn::<Curve25519Scalar>::BigInt(
column_a[3..].to_vec(),
)];
assert!(matches!(
commitments.try_append_rows_with_offset(&new_columns, 3, &()),
Err(NumColumnsMismatch)
));
let new_columns = vec![
OwnedColumn::<Curve25519Scalar>::BigInt(column_a[3..].to_vec()),
OwnedColumn::VarChar(column_b[3..].to_vec()),
OwnedColumn::BigInt(column_a[3..].to_vec()),
];
assert!(matches!(
commitments.try_append_rows_with_offset(&new_columns, 3, &()),
Err(NumColumnsMismatch)
));
}
#[test]
fn we_can_extend_columns() {
let column_a = [12i64, 34, 56];
let column_b = ["Lorem", "ipsum", "dolor"].map(String::from);
let column_c = ["sit", "amet", "consectetur"].map(String::from);
let column_d = [78i64, 90, 1112];
let columns = vec![
OwnedColumn::<Curve25519Scalar>::BigInt(column_a.to_vec()),
OwnedColumn::VarChar(column_b.to_vec()),
];
let mut commitments = Vec::<RistrettoPoint>::from_columns_with_offset(&columns, 0, &());
let new_columns = vec![
OwnedColumn::<Curve25519Scalar>::VarChar(column_c.to_vec()),
OwnedColumn::BigInt(column_d.to_vec()),
];
commitments.extend_columns_with_offset(&new_columns, 0, &());
let mut expected_commitments = vec![CompressedRistretto::default(); 4];
compute_curve25519_commitments(
&mut expected_commitments,
&[
Sequence::from(column_a.as_slice()),
Sequence::from(
column_b
.map(Curve25519Scalar::from)
.map(<[u64; 4]>::from)
.as_slice(),
),
Sequence::from(
column_c
.map(Curve25519Scalar::from)
.map(<[u64; 4]>::from)
.as_slice(),
),
Sequence::from(column_d.as_slice()),
],
0,
);
let expected_commitments =
Vec::from_iter(expected_commitments.iter().map(|c| c.decompress().unwrap()));
assert_eq!(commitments, expected_commitments);
}
#[test]
fn we_can_add_commitment_collections() {
let column_a = [12i64, 34, 56, 78, 90];
let column_b = ["Lorem", "ipsum", "dolor", "sit", "amet"].map(String::from);
let columns = vec![
OwnedColumn::<Curve25519Scalar>::BigInt(column_a[..3].to_vec()),
OwnedColumn::VarChar(column_b[..3].to_vec()),
];
let commitments_a = Vec::<RistrettoPoint>::from_columns_with_offset(&columns, 0, &());
let new_columns = vec![
OwnedColumn::<Curve25519Scalar>::BigInt(column_a[3..].to_vec()),
OwnedColumn::VarChar(column_b[3..].to_vec()),
];
let commitments_b = Vec::<RistrettoPoint>::from_columns_with_offset(&new_columns, 3, &());
let commitments = commitments_a.try_add(commitments_b).unwrap();
let mut expected_commitments = vec![CompressedRistretto::default(); 2];
compute_curve25519_commitments(
&mut expected_commitments,
&[
Sequence::from(column_a.as_slice()),
Sequence::from(
column_b
.map(Curve25519Scalar::from)
.map(<[u64; 4]>::from)
.as_slice(),
),
],
0,
);
let expected_commitments =
Vec::from_iter(expected_commitments.iter().map(|c| c.decompress().unwrap()));
assert_eq!(commitments, expected_commitments);
}
#[test]
fn we_cannot_add_commitment_collections_of_mixed_column_counts() {
let column_a = [12i64, 34, 56, 78, 90];
let column_b = ["Lorem", "ipsum", "dolor", "sit", "amet"].map(String::from);
let columns = vec![
OwnedColumn::<Curve25519Scalar>::BigInt(column_a[..3].to_vec()),
OwnedColumn::VarChar(column_b[..3].to_vec()),
];
let commitments = Vec::<RistrettoPoint>::from_columns_with_offset(&columns, 0, &());
let new_columns = Vec::<Column<Curve25519Scalar>>::new();
let new_commitments = Vec::<RistrettoPoint>::from_columns_with_offset(&new_columns, 3, &());
assert!(matches!(
commitments.clone().try_add(new_commitments),
Err(NumColumnsMismatch)
));
let new_columns = vec![OwnedColumn::<Curve25519Scalar>::BigInt(
column_a[3..].to_vec(),
)];
let new_commitments = Vec::<RistrettoPoint>::from_columns_with_offset(&new_columns, 3, &());
assert!(matches!(
commitments.clone().try_add(new_commitments),
Err(NumColumnsMismatch)
));
let new_columns = vec![
OwnedColumn::<Curve25519Scalar>::BigInt(column_a[3..].to_vec()),
OwnedColumn::VarChar(column_b[3..].to_vec()),
OwnedColumn::BigInt(column_a[3..].to_vec()),
];
let new_commitments = Vec::<RistrettoPoint>::from_columns_with_offset(&new_columns, 3, &());
assert!(matches!(
commitments.try_add(new_commitments),
Err(NumColumnsMismatch)
));
}
#[test]
fn we_can_sub_commitment_collections() {
let column_a = [12i64, 34, 56, 78, 90];
let column_b = ["Lorem", "ipsum", "dolor", "sit", "amet"].map(String::from);
let columns = vec![
OwnedColumn::<Curve25519Scalar>::BigInt(column_a[..3].to_vec()),
OwnedColumn::VarChar(column_b[..3].to_vec()),
];
let commitments_a = Vec::<RistrettoPoint>::from_columns_with_offset(&columns, 0, &());
let full_columns = vec![
OwnedColumn::<Curve25519Scalar>::BigInt(column_a.to_vec()),
OwnedColumn::VarChar(column_b.to_vec()),
];
let commitments_b = Vec::<RistrettoPoint>::from_columns_with_offset(&full_columns, 0, &());
let commitments = commitments_b.try_sub(commitments_a).unwrap();
let mut expected_commitments = vec![CompressedRistretto::default(); 2];
compute_curve25519_commitments(
&mut expected_commitments,
&[
Sequence::from(&column_a[3..]),
Sequence::from(&column_b.map(Curve25519Scalar::from).map(<[u64; 4]>::from)[3..]),
],
3,
);
let expected_commitments =
Vec::from_iter(expected_commitments.iter().map(|c| c.decompress().unwrap()));
assert_eq!(commitments, expected_commitments);
}
#[test]
fn we_cannot_sub_commitment_collections_of_mixed_column_counts() {
let column_a = [12i64, 34, 56, 78, 90];
let column_b = ["Lorem", "ipsum", "dolor", "sit", "amet"].map(String::from);
let columns = vec![
OwnedColumn::<Curve25519Scalar>::BigInt(column_a[..3].to_vec()),
OwnedColumn::VarChar(column_b[..3].to_vec()),
];
let commitments = Vec::<RistrettoPoint>::from_columns_with_offset(&columns, 0, &());
let full_columns = Vec::<Column<Curve25519Scalar>>::new();
let full_commitments =
Vec::<RistrettoPoint>::from_columns_with_offset(&full_columns, 0, &());
assert!(matches!(
full_commitments.clone().try_sub(commitments.clone()),
Err(NumColumnsMismatch)
));
let full_columns = vec![OwnedColumn::<Curve25519Scalar>::BigInt(column_a.to_vec())];
let full_commitments =
Vec::<RistrettoPoint>::from_columns_with_offset(&full_columns, 0, &());
assert!(matches!(
full_commitments.try_sub(commitments.clone()),
Err(NumColumnsMismatch)
));
let full_columns = vec![
OwnedColumn::<Curve25519Scalar>::BigInt(column_a.to_vec()),
OwnedColumn::VarChar(column_b.to_vec()),
OwnedColumn::BigInt(column_a.to_vec()),
];
let full_commitments =
Vec::<RistrettoPoint>::from_columns_with_offset(&full_columns, 0, &());
assert!(matches!(
full_commitments.try_sub(commitments),
Err(NumColumnsMismatch)
));
}
}