use super::*;
use crate::*;
pub struct BDLOPLinearNIZKArg<const N: usize, E: FieldScalar> {
pub g: Polynomial<N, E>,
pub c_a: BDLOP<N, E>,
pub c_b: BDLOP<N, E>,
pub t_1: Vector<Polynomial<N, E>>,
pub t_2: Vector<Polynomial<N, E>>,
pub u: Vector<Polynomial<N, E>>,
pub z_1: Vector<Polynomial<N, E>>,
pub z_2: Vector<Polynomial<N, E>>,
}
impl<const N: usize, E: FieldScalar> BDLOPLinearNIZKArg<N, E> {
pub fn new<R: Rng>(
(c_a, r_a): (BDLOP<N, E>, &Vector<Polynomial<N, E>>),
(c_b, r_b): (BDLOP<N, E>, &Vector<Polynomial<N, E>>),
g: Polynomial<N, E>,
rng: &mut R,
) -> Result<Self> {
assert!(c_a.a_1 == c_b.a_1);
assert!(c_a.a_2 == c_b.a_2);
let a_1 = &c_a.a_1;
let a_2 = &c_a.a_2;
loop {
let y_1 = (0..a_1.width())
.map(|_| Polynomial::sample_gaussian(SIGMA, rng).into_eval_form())
.collect::<Vector<_>>();
let y_2 = (0..a_1.width())
.map(|_| Polynomial::sample_gaussian(SIGMA, rng).into_eval_form())
.collect::<Vector<_>>();
let t_1 = a_1.as_ref() * &y_1;
let t_2 = a_1.as_ref() * &y_2;
let u = g.batch_mul(a_2.as_ref() * &y_1) - a_2.as_ref() * &y_2;
let hash = blake3::hash(
&t_1.iter()
.chain(t_2.iter())
.chain(u.iter())
.flat_map(|p| p.as_le_bytes())
.collect::<Vec<_>>(),
);
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_1 = y_1 + &(d.batch_mul(r_a.clone()));
let z_2 = y_2 + &(d.batch_mul(r_b.clone()));
return Ok(Self {
c_a,
c_b,
t_1,
t_2,
u,
g,
z_1,
z_2,
});
}
}
pub fn verify(&self, g: Polynomial<N, E>) -> Result<()> {
if g != self.g {
anyhow::bail!("BDLOPLinearNIZKArg g value mismatch");
}
let l2_norm_bound = 4.0 * SIGMA * (N as f64).sqrt();
for (z_1, z_2) in self.z_1.iter().zip(self.z_2.iter()) {
if z_1.norm_l2() >= l2_norm_bound {
anyhow::bail!("BDLOPLinearNIZKArg z_1 is beyond bounds");
}
if z_2.norm_l2() >= l2_norm_bound {
anyhow::bail!("BDLOPLinearNIZKArg z_2 is beyond bounds");
}
}
let hash = blake3::hash(
&self
.t_1
.iter()
.chain(self.t_2.iter())
.chain(self.u.iter())
.flat_map(|p| p.as_le_bytes())
.collect::<Vec<_>>(),
);
let mut csprng = rand_chacha::ChaCha20Rng::from_seed(hash.into());
let d: Polynomial<N, E> = std::array::from_fn(|_| ternary_sample(&mut csprng)).into();
if self.c_a.a_1.as_ref() * &self.z_1 != self.t_1.clone() + &(self.c_a.c_1.clone() * d) {
anyhow::bail!("BDLOPLinearNIZKArg z_1 equality mismatch");
}
if self.c_a.a_1.as_ref() * &self.z_2 != self.t_2.clone() + &(self.c_b.c_1.clone() * d) {
anyhow::bail!("BDLOPLinearNIZKArg z_2 equality mismatch");
}
let lhs = self.c_a.a_2.as_ref() * &self.z_1 * self.g - self.c_a.a_2.as_ref() * &self.z_2;
let rhs = (self.c_a.c_2.clone() * self.g - &self.c_b.c_2) * d + &self.u;
if lhs != rhs {
anyhow::bail!("BDLOPLinearNIZKArg final equality mismatch");
}
Ok(())
}
}
#[test]
fn bdlop_open_linear_zk() -> Result<()> {
type Field = MilliScalarMont;
const RING_DEGREE: usize = 512;
let rng = &mut rand::rng();
let msg_len = 1;
let lattice = BDLOP::<RING_DEGREE, Field>::lattice_for(msg_len, rng);
let g = Polynomial::<RING_DEGREE, Field>::sample_uniform(rng);
let a = Vector::sample_uniform(msg_len, rng);
let b = a.clone() * g;
let (c_a, r_a) = BDLOP::commit(a, &lattice, rng);
let (c_b, r_b) = BDLOP::commit(b, &lattice, rng);
let arg = BDLOPLinearNIZKArg::new((c_a, &r_a), (c_b, &r_b), g, rng)?;
arg.verify(g)?;
Ok(())
}