mod nizk;
pub use nizk::*;
use std::sync::Arc;
use rand::SeedableRng;
use crate::*;
const SIGMA: f64 = 27000f64;
#[derive(Clone, Debug)]
pub struct BDLOP<const N: usize, E: FieldScalar> {
pub a_1: Arc<Matrix<Polynomial<N, E>>>,
pub a_2: Arc<Matrix<Polynomial<N, E>>>,
pub c_1: Vector<Polynomial<N, E>>,
pub c_2: Vector<Polynomial<N, E>>,
}
impl<const N: usize, E: FieldScalar> BDLOP<N, E> {
pub fn as_le_bytes(&self) -> impl Iterator<Item = u8> {
self.c_1
.iter()
.flat_map(|v| v.as_le_bytes())
.chain(self.c_2.iter().flat_map(|v| v.as_le_bytes()))
}
fn dimension(msg_len: usize) -> (usize, usize) {
let a_1_height = msg_len;
let width = a_1_height + msg_len;
(a_1_height, width)
}
pub fn lattice_for<R: Rng>(
msg_len: usize,
rng: &mut R,
) -> (Arc<Matrix<Polynomial<N, E>>>, Arc<Matrix<Polynomial<N, E>>>) {
let (a_1_height, width) = Self::dimension(msg_len);
let a_1 = Matrix::identity(a_1_height).compose_horizontal(Matrix::random(
a_1_height,
width - a_1_height,
rng,
));
let a_2 = Matrix::zero(msg_len, a_1_height)
.compose_horizontal(Matrix::identity(msg_len))
.compose_horizontal(Matrix::random(msg_len, width - a_1_height - msg_len, rng));
(Arc::new(a_1), Arc::new(a_2))
}
pub fn commit<R: Rng>(
val: Vector<Polynomial<N, E>>,
lattice: &(Arc<Matrix<Polynomial<N, E>>>, Arc<Matrix<Polynomial<N, E>>>),
rng: &mut R,
) -> (Self, Vector<Polynomial<N, E>>) {
let (a_1, a_2) = lattice.clone();
let r = (0..a_1.width())
.map(|_| {
let mut p = Polynomial::default();
for coef in p.coefs_mut() {
*coef = ternary_sample(rng);
}
p
})
.collect::<Vector<_>>();
let c_1 = a_1.as_ref() * &r;
let c_2 = (a_2.as_ref() * &r) + &val;
(Self { a_1, a_2, c_1, c_2 }, r)
}
pub fn try_open(&self, r: &Vector<Polynomial<N, E>>) -> Result<Vector<Polynomial<N, E>>> {
if self.a_1.as_ref() * r != self.c_1 {
anyhow::bail!("Failed to open commitment, secret is incorrect");
}
Ok(&self.c_2 - self.a_2.as_ref() * &r)
}
pub fn try_open_zk<R: Rng>(
&self,
r: &Vector<Polynomial<N, E>>,
rng: &mut R,
) -> Result<(
Polynomial<N, E>,
Vector<Polynomial<N, E>>,
Vector<Polynomial<N, E>>,
)> {
if self.a_1.as_ref() * r != self.c_1 {
anyhow::bail!("Failed to open commitment, secret is incorrect");
}
loop {
let y = (0..self.a_1.width())
.map(|_| Polynomial::sample_gaussian(SIGMA, rng))
.collect::<Vector<_>>();
let t = self.a_1.as_ref() * &y;
let hash = blake3::hash(&t.iter().flat_map(|v| v.as_le_bytes()).collect::<Vec<u8>>());
let mut csprng = rand_chacha::ChaCha20Rng::from_seed(hash.into());
let d: Polynomial<N, E> = std::array::from_fn(|_| ternary_sample(&mut csprng)).into();
let z = y.clone() + &(d.batch_mul(r.clone()));
return Ok((d, t, z));
}
}
}
impl<const N: usize, E: FieldScalar> Add<&Self> for BDLOP<N, E> {
type Output = Self;
fn add(mut self, rhs: &Self) -> Self::Output {
self += rhs;
self
}
}
impl<const N: usize, E: FieldScalar> AddAssign<&Self> for BDLOP<N, E> {
fn add_assign(&mut self, rhs: &Self) {
assert!(
self.a_1 == rhs.a_1 && self.a_2 == rhs.a_2,
"BDLOP: refusing to add commitments on different lattices"
);
self.c_1 += &rhs.c_1;
self.c_2 += &rhs.c_2;
}
}
#[test]
fn bdlop_commit_var_dimension() -> Result<()> {
type Field = MilliScalarMont;
const RING_DEGREE: usize = 64;
let rng = &mut rand::rng();
for i in 1..10 {
let lattice = BDLOP::<RING_DEGREE, Field>::lattice_for(i, rng);
let (commitment, r) = BDLOP::commit(Vector::sample_uniform(i, rng), &lattice, rng);
commitment
.try_open(&r)
.expect("failed to open BDLOP commitment");
commitment
.try_open(&(r.clone() + &r))
.expect_err("should fail to open BDLOP commitment to bad r value");
}
Ok(())
}
#[test]
fn bdlop_open_zk() -> Result<()> {
type Field = MilliScalarMont;
const RING_DEGREE: usize = 128;
let rng = &mut rand::rng();
let msg_len = 1;
let lattice = BDLOP::<RING_DEGREE, Field>::lattice_for(msg_len, rng);
let msg = Vector::sample_uniform(msg_len, rng);
let (c, r) = BDLOP::commit(msg, &lattice, rng);
let (d, t, z) = c.try_open_zk(&r, rng)?;
for p in &z {
let max_l2 = 4.0 * SIGMA * (RING_DEGREE as f64).sqrt();
assert!(p.norm_l2() < max_l2);
}
assert_eq!(c.a_1.as_ref() * &z, t + &(c.c_1 * d));
Ok(())
}