use super::ipa;
use crate::transcript::Transcript;
use bytes::{Buf, BufMut};
use commonware_codec::{Encode, EncodeSize, Error, Read, Write};
use commonware_math::{
algebra::{powers, Additive, CryptoGroup, Field, HashToGroup, Random, Ring, Space},
synthetic::Synthetic,
};
use commonware_parallel::{Sequential, Strategy};
use rand_core::CryptoRng;
use std::{
collections::BTreeMap,
ops::{Index, IndexMut, Mul},
};
pub struct SparseMatrix<F> {
width: usize,
height: usize,
weights: BTreeMap<(usize, usize), F>,
zero: F,
}
impl<F> SparseMatrix<F> {
pub const fn width(&self) -> usize {
self.width
}
pub const fn height(&self) -> usize {
self.height
}
pub fn pad(&mut self, width: usize, height: usize) {
self.width = self.width.max(width);
self.height = self.height.max(height);
}
}
impl<F> IntoIterator for SparseMatrix<F> {
type Item = ((usize, usize), F);
type IntoIter = <BTreeMap<(usize, usize), F> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.weights.into_iter()
}
}
impl<F: Additive> Default for SparseMatrix<F> {
fn default() -> Self {
Self {
width: 0,
height: 0,
weights: Default::default(),
zero: F::zero(),
}
}
}
impl<F: Additive> Index<(usize, usize)> for SparseMatrix<F> {
type Output = F;
fn index(&self, idx: (usize, usize)) -> &Self::Output {
self.weights.get(&idx).unwrap_or(&self.zero)
}
}
impl<F: Additive> IndexMut<(usize, usize)> for SparseMatrix<F> {
fn index_mut(&mut self, idx: (usize, usize)) -> &mut Self::Output {
self.height = self
.height
.max(idx.0.checked_add(1).expect("row index overflow"));
self.width = self
.width
.max(idx.1.checked_add(1).expect("column index overflow"));
self.weights.entry(idx).or_insert(F::zero())
}
}
impl<F: Ring> Mul<&[F]> for &SparseMatrix<F> {
type Output = Vec<F>;
fn mul(self, rhs: &[F]) -> Self::Output {
let mut out = vec![F::zero(); self.height];
for (&(i, j), weight) in &self.weights {
let Some(value) = rhs.get(j) else {
continue;
};
out[i] += &(weight.clone() * value);
}
out
}
}
impl<F: Write> Write for SparseMatrix<F> {
fn write(&self, buf: &mut impl BufMut) {
self.weights.write(buf);
}
}
impl<F: EncodeSize> EncodeSize for SparseMatrix<F> {
fn encode_size(&self) -> usize {
self.weights.encode_size()
}
}
pub struct Circuit<F> {
committed_vars: usize,
internal_vars: usize,
weights: SparseMatrix<F>,
}
impl<F: Write> Write for Circuit<F> {
fn write(&self, buf: &mut impl BufMut) {
self.committed_vars.write(buf);
self.weights.write(buf);
}
}
impl<F: Encode> Circuit<F> {
fn commit(&self, transcript: &mut Transcript) {
transcript.commit(self.encode());
}
}
impl<F: EncodeSize> EncodeSize for Circuit<F> {
fn encode_size(&self) -> usize {
self.committed_vars.encode_size() + self.weights.encode_size()
}
}
impl<F: Ring> Circuit<F> {
pub fn new(committed_vars: usize, weights: SparseMatrix<F>) -> Option<Self> {
let remaining_vars = weights.width.checked_sub(committed_vars.checked_add(1)?)?;
if remaining_vars % 3 != 0 {
return None;
}
let internal_vars = remaining_vars / 3;
Some(Self {
committed_vars,
internal_vars,
weights,
})
}
pub const fn internal_vars(&self) -> usize {
self.internal_vars
}
pub const fn committed_vars(&self) -> usize {
self.committed_vars
}
#[must_use]
pub fn is_satisfied(
&self,
committed_values: &[F],
left_values: &[F],
right_values: &[F],
) -> bool {
if committed_values.len() != self.committed_vars
|| left_values.len() != self.internal_vars
|| right_values.len() != self.internal_vars
{
return false;
}
let mut output = Vec::with_capacity(1 + self.committed_vars + 3 * self.internal_vars);
output.push(F::one());
output.extend_from_slice(committed_values);
output.extend_from_slice(left_values);
output.extend_from_slice(right_values);
output.extend(
left_values
.iter()
.zip(right_values)
.map(|(l_i, r_i)| l_i.clone() * r_i),
);
let mut res = vec![F::zero(); self.weights.height];
for (&(i, j), w_ij) in &self.weights.weights {
res[i] += &(output[j].clone() * w_ij);
}
let zero = F::zero();
res.iter().all(|r_i| r_i == &zero)
}
}
mod zkc {
use crate::zk::circuit as zk;
use commonware_math::algebra::{Field, Random, Ring};
use commonware_utils::ordered::Map;
use rand_core::CryptoRng;
use std::{borrow::Cow, collections::BTreeMap};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum Location {
One,
Witness(usize),
Left(usize),
Right(usize),
Output(usize),
Committed(usize),
}
#[derive(Clone)]
enum LinComb<F> {
Location(Location),
Constant(F),
General(Map<Location, F>),
}
impl<F: Ring> LinComb<F> {
fn mul(&self, other: &Self) -> Option<Self> {
let (constant, other) = match (self, other) {
(Self::Constant(c), other) => (c.clone(), other),
(other, Self::Constant(c)) => (c.clone(), other),
_ => return None,
};
let out = match other {
Self::Location(location) => Self::General(
Map::try_from([(*location, constant)])
.expect("single entry cannot duplicate keys"),
),
Self::Constant(other_constant) => Self::Constant(constant * other_constant),
Self::General(items) => {
let mut items = items.clone();
for w in items.values_mut() {
*w = w.clone() * &constant;
}
Self::General(items)
}
};
Some(out)
}
fn sum(&self, other: &Self) -> Self {
let mut terms: Vec<(Location, F)> = self
.iter()
.chain(other.iter())
.map(|(w, loc)| (loc, w.into_owned()))
.collect();
terms.sort_by_key(|&(loc, _)| loc);
let mut merged: Vec<(Location, F)> = Vec::with_capacity(terms.len());
for (loc, w) in terms {
match merged.last_mut() {
Some((last, acc)) if *last == loc => *acc += &w,
_ => merged.push((loc, w)),
}
}
Self::General(Map::try_from(merged).expect("merged locations should be unique"))
}
fn iter(&self) -> impl Iterator<Item = (Cow<'_, F>, Location)> {
let (single, general) = match self {
Self::Location(loc) => (Some((Cow::Owned(F::one()), *loc)), None),
Self::Constant(w) => (Some((Cow::Borrowed(w), Location::One)), None),
Self::General(items) => (None, Some(items.iter_pairs())),
};
single.into_iter().chain(
general
.into_iter()
.flatten()
.map(|(loc, w)| (Cow::Borrowed(w), *loc)),
)
}
const fn witness(&self) -> Option<usize> {
match self {
Self::Location(Location::Witness(w)) => Some(*w),
_ => None,
}
}
}
pub struct ZKCConverter<F> {
linearize_queue: Vec<zk::CircuitIdx>,
linearize_cache: BTreeMap<zk::CircuitIdx, LinComb<F>>,
assertions: Vec<(zk::CircuitIdx, zk::CircuitIdx)>,
extra_assertions: Vec<(zk::CircuitIdx, Location)>,
witness_locations: BTreeMap<usize, Location>,
committed_indices: Vec<zk::CircuitIdx>,
committed_positions: BTreeMap<zk::CircuitIdx, usize>,
internal_vars: Vec<(
zk::CircuitIdx,
Option<zk::CircuitIdx>,
Option<zk::CircuitIdx>,
)>,
}
impl<F: Field + Random> ZKCConverter<F> {
pub fn new(committed_indices: Vec<zk::CircuitIdx>) -> Self {
let mut committed_positions = BTreeMap::new();
for (i, &idx) in committed_indices.iter().enumerate() {
committed_positions.entry(idx).or_insert(i);
}
Self {
linearize_queue: Vec::new(),
linearize_cache: BTreeMap::new(),
assertions: Vec::new(),
extra_assertions: Vec::new(),
witness_locations: BTreeMap::new(),
committed_indices,
committed_positions,
internal_vars: Default::default(),
}
}
pub fn circuit(mut self, zkc: zk::Circuit<F>) -> super::Circuit<F> {
self.populate(&zkc);
self.reckon_circuit()
}
pub fn circuit_and_witness(
mut self,
blinding_rng: Option<&mut impl CryptoRng>,
zkc: zk::ValuedCircuit<F>,
) -> (super::Circuit<F>, super::Witness<F>) {
self.populate(&zkc.circuit);
let blinding = blinding_rng.map_or_else(
|| vec![F::zero(); self.committed_indices.len()],
|rng| {
(0..self.committed_indices.len())
.map(|_| F::random(&mut *rng))
.collect::<Vec<_>>()
},
);
let values = self
.committed_indices
.iter()
.map(|&i| zkc[i].clone())
.collect::<Vec<_>>();
let mut left = Vec::with_capacity(self.internal_vars.len());
let mut right = Vec::with_capacity(self.internal_vars.len());
let mut out = Vec::with_capacity(self.internal_vars.len());
for &(l_i, r_i, o_i) in &self.internal_vars {
left.push(zkc[l_i].clone());
match (r_i, o_i) {
(None, _) => {
right.push(F::zero());
out.push(F::zero());
}
(Some(r_i), None) => {
right.push(zkc[r_i].clone());
out.push(zkc[l_i].clone() * &zkc[r_i]);
}
(Some(r_i), Some(o_i)) => {
right.push(zkc[r_i].clone());
out.push(zkc[o_i].clone());
}
}
}
let witness = super::Witness {
values,
blinding,
left,
right,
out,
};
(self.reckon_circuit(), witness)
}
fn populate(&mut self, zkc: &zk::Circuit<F>) {
for &(l, r) in &zkc.assertions {
self.assertions.push((l, r));
self.linearize(zkc, l);
self.linearize(zkc, r);
}
{
let mut left = true;
for loc in self.witness_locations.values_mut() {
let &mut Location::Witness(w) = loc else {
continue;
};
if left {
*loc = Location::Left(self.internal_vars.len());
self.internal_vars
.push((zk::CircuitIdx::Witness(w as u32), None, None));
left = false;
} else {
let i = self.internal_vars.len() - 1;
*loc = Location::Right(i);
self.internal_vars[i].1 = Some(zk::CircuitIdx::Witness(w as u32));
left = true;
}
}
}
let committed = self.committed_indices.clone();
for (i, c_pos) in committed.into_iter().enumerate() {
self.linearize(zkc, c_pos);
if matches!(
self.linearize_cache.get(&c_pos),
Some(LinComb::Location(Location::Committed(_)))
) {
let k = self.internal_vars.len();
self.internal_vars.push((c_pos, None, None));
self.linearize_cache
.insert(c_pos, LinComb::Location(Location::Left(k)));
}
self.extra_assertions.push((c_pos, Location::Committed(i)));
}
}
fn location_to_col(&self, loc: Location) -> usize {
fn inner<F>(this: &ZKCConverter<F>, loc: Location, no_witness: bool) -> usize {
match loc {
Location::One => 0,
Location::Left(i) => 1 + this.committed_indices.len() + i,
Location::Right(i) => {
1 + this.committed_indices.len() + this.internal_vars.len() + i
}
Location::Output(i) => {
1 + this.committed_indices.len() + 2 * this.internal_vars.len() + i
}
Location::Committed(i) => 1 + i,
Location::Witness(i) => {
if no_witness {
unreachable!("unexpected witness location")
} else {
inner(this, this.witness_locations[&i], true)
}
}
}
}
inner(self, loc, false)
}
fn reckon_circuit(&self) -> super::Circuit<F> {
let mut weights = super::SparseMatrix::default();
for (row, (l, r)) in self
.assertions
.iter()
.map(|(l, r)| {
(
Cow::Borrowed(
self.linearize_cache
.get(l)
.expect("linearize_cache_should_be_populated"),
),
Cow::Borrowed(
self.linearize_cache
.get(r)
.expect("linearize_cache should be populated"),
),
)
})
.chain(self.extra_assertions.iter().map(|(cidx, loc)| {
(
Cow::Borrowed(
self.linearize_cache
.get(cidx)
.expect("linearize_cache should be populated"),
),
Cow::Owned(LinComb::Location(*loc)),
)
}))
.enumerate()
{
for (w, loc) in l.iter() {
weights[(row, self.location_to_col(loc))] += w.as_ref();
}
for (w, loc) in r.iter() {
weights[(row, self.location_to_col(loc))] -= w.as_ref();
}
}
super::Circuit {
committed_vars: self.committed_indices.len(),
internal_vars: self.internal_vars.len(),
weights,
}
}
fn assign_witness_location(&mut self, witness: usize, loc: Location) -> Location {
*self
.witness_locations
.entry(witness)
.and_modify(|current_loc| {
if let Location::Witness(_) = *current_loc {
*current_loc = loc;
}
})
.or_insert(loc)
}
fn linearize(&mut self, zkc: &zk::Circuit<F>, i: zk::CircuitIdx) {
self.linearize_queue.clear();
self.linearize_queue.push(i);
while let Some(i) = self.linearize_queue.pop() {
if self.linearize_cache.contains_key(&i) {
continue;
}
let comb = match i {
zk::CircuitIdx::Constant(i) => {
LinComb::Constant(zkc.constants[i as usize].clone())
}
this @ zk::CircuitIdx::Witness(i) => {
let i_usize = i as usize;
let w_loc = self
.committed_positions
.get(&this)
.map_or(Location::Witness(i_usize), |&i| Location::Committed(i));
let loc = self.assign_witness_location(i_usize, w_loc);
LinComb::Location(loc)
}
this @ zk::CircuitIdx::Node(i) => {
let this_node = &zkc.nodes[i as usize];
let (l, r) = match *this_node {
zk::CircuitNode::Add(l, r) => (l, r),
zk::CircuitNode::Mul(l, r) => (l, r),
};
let (l_comb, r_comb) =
match (self.linearize_cache.get(&l), self.linearize_cache.get(&r)) {
(None, None) => {
self.linearize_queue.extend([this, r, l]);
continue;
}
(Some(_), None) => {
self.linearize_queue.extend([this, r]);
continue;
}
(None, Some(_)) => {
self.linearize_queue.extend([this, l]);
continue;
}
(Some(l_comb), Some(r_comb)) => (l_comb, r_comb),
};
match *this_node {
zk::CircuitNode::Add(_, _) => l_comb.sum(r_comb),
zk::CircuitNode::Mul(l, r) => {
if let Some(out) = l_comb.mul(r_comb) {
out
} else {
let i = self.internal_vars.len();
self.internal_vars.push((l, Some(r), Some(this)));
self.extra_assertions.push((l, Location::Left(i)));
self.extra_assertions.push((r, Location::Right(i)));
let w_l = l_comb.witness();
let w_r = r_comb.witness();
if let Some(w) = w_l {
self.assign_witness_location(w, Location::Left(i));
}
if let Some(w) = w_r {
self.assign_witness_location(w, Location::Right(i));
}
LinComb::Location(Location::Output(i))
}
}
}
}
};
self.linearize_cache.insert(i, comb);
}
}
}
}
pub fn zkc_to_circuit<F: Field + Random>(
zkc: crate::zk::circuit::Circuit<F>,
committed_indices: &[crate::zk::circuit::CircuitIdx],
) -> Circuit<F> {
zkc::ZKCConverter::new(committed_indices.to_vec()).circuit(zkc)
}
pub fn zkc_to_circuit_and_witness<F: Field + Random>(
blinding_rng: Option<&mut impl CryptoRng>,
zkc: crate::zk::circuit::ValuedCircuit<F>,
committed_indices: &[crate::zk::circuit::CircuitIdx],
) -> (Circuit<F>, Witness<F>) {
zkc::ZKCConverter::new(committed_indices.to_vec()).circuit_and_witness(blinding_rng, zkc)
}
#[derive(PartialEq)]
pub struct Setup<G> {
ipa: ipa::Setup<G>,
value_generator: G,
blinding_generator: G,
}
impl<G> Setup<G> {
pub const fn new(ipa: ipa::Setup<G>, value_generator: G, blinding_generator: G) -> Self {
Self {
ipa,
value_generator,
blinding_generator,
}
}
pub const fn value_generator(&self) -> &G {
&self.value_generator
}
pub const fn blinding_generator(&self) -> &G {
&self.blinding_generator
}
pub const fn supports(&self, lg_len: u8) -> bool {
self.ipa.supports(lg_len)
}
pub fn hashed(domain_separator: &[u8], lg_len: u8, value_generator: G) -> Self
where
G: HashToGroup,
{
let n: usize = 1usize << lg_len;
let product_generator = G::hash_to_group(domain_separator, b"product");
let blinding_generator = G::hash_to_group(domain_separator, b"blinding");
let g_and_h = (0..n).map(|i| {
let i_bytes = (i as u64).to_le_bytes();
let mut g_msg = Vec::with_capacity(2 + i_bytes.len());
g_msg.extend_from_slice(b"g/");
g_msg.extend_from_slice(&i_bytes);
let mut h_msg = Vec::with_capacity(2 + i_bytes.len());
h_msg.extend_from_slice(b"h/");
h_msg.extend_from_slice(&i_bytes);
(
G::hash_to_group(domain_separator, &g_msg),
G::hash_to_group(domain_separator, &h_msg),
)
});
Self::new(
ipa::Setup::new(product_generator, g_and_h),
value_generator,
blinding_generator,
)
}
fn build_virtual<F: Field>(&self) -> (Setup<Synthetic<F, G>>, Vec<G>)
where
G: Clone,
{
let n = self.ipa.g().len();
let mut gens = Synthetic::<F, G>::generators();
let vg: Vec<_> = (0..n)
.map(|_| gens.next().expect("generators is infinite"))
.collect();
let vh: Vec<_> = (0..n)
.map(|_| gens.next().expect("generators is infinite"))
.collect();
let vq = gens.next().expect("generators is infinite");
let ipa_vs = ipa::Setup::new(vq, vg.into_iter().zip(vh));
let pv = gens.next().expect("generators is infinite");
let pb = gens.next().expect("generators is infinite");
let vs = Setup::new(ipa_vs, pv, pb);
let mut flat = Vec::with_capacity(2 * n + 3);
flat.extend_from_slice(self.ipa.g());
flat.extend_from_slice(self.ipa.h());
flat.push(self.ipa.product_generator().clone());
flat.push(self.value_generator.clone());
flat.push(self.blinding_generator.clone());
(vs, flat)
}
pub fn eval<F: Field>(
&self,
f: impl FnOnce(&Setup<Synthetic<F, G>>) -> Option<Synthetic<F, G>>,
strategy: &impl Strategy,
) -> Option<G>
where
G: Space<F>,
{
let (vs, flat) = self.build_virtual::<F>();
f(&vs).map(|v| v.eval(&flat, strategy))
}
pub fn eval_check_batched<F: Field + Random, R: CryptoRng>(
&self,
rng: &mut R,
f: impl FnOnce(&Setup<Synthetic<F, G>>, &mut R) -> Option<Vec<Option<Synthetic<F, G>>>>,
strategy: &impl Strategy,
) -> Option<Vec<bool>>
where
G: Space<F> + PartialEq,
{
let (vs, flat) = self.build_virtual::<F>();
let synths = f(&vs, &mut *rng)?;
let n = synths.len();
let scaled: Vec<Option<Synthetic<F, G>>> = synths
.into_iter()
.map(|opt| opt.map(|s| s * &F::random(&mut *rng)))
.collect();
let active: Vec<usize> = (0..n).filter(|&i| scaled[i].is_some()).collect();
let check = |range: &[usize]| -> bool {
let mut acc = Synthetic::<F, G>::default();
for &i in range {
acc += scaled[i].as_ref().expect("active indices are Some");
}
acc.eval(&flat, strategy) == G::zero()
};
let mut valid = vec![false; n];
let mut stack: Vec<&[usize]> = Vec::new();
if !active.is_empty() {
stack.push(&active);
}
while let Some(range) = stack.pop() {
if check(range) {
for &i in range {
valid[i] = true;
}
} else if range.len() > 1 {
let mid = range.len() / 2;
let (left, right) = range.split_at(mid);
stack.push(right);
stack.push(left);
}
}
Some(valid)
}
}
impl<G: Write> Write for Setup<G> {
fn write(&self, buf: &mut impl BufMut) {
self.ipa.write(buf);
self.value_generator.write(buf);
self.blinding_generator.write(buf);
}
}
impl<G: EncodeSize> EncodeSize for Setup<G> {
fn encode_size(&self) -> usize {
self.ipa.encode_size()
+ self.value_generator.encode_size()
+ self.blinding_generator.encode_size()
}
}
impl<G: Read> Read for Setup<G>
where
G::Cfg: Clone,
{
type Cfg = (usize, G::Cfg);
fn read_cfg(buf: &mut impl Buf, (max_len, cfg): &Self::Cfg) -> Result<Self, Error> {
let ipa = ipa::Setup::read_cfg(buf, &(*max_len, cfg.clone()))?;
let value_generator = G::read_cfg(buf, cfg)?;
let blinding_generator = G::read_cfg(buf, cfg)?;
Ok(Self::new(ipa, value_generator, blinding_generator))
}
}
#[allow(dead_code)]
pub struct Witness<F> {
values: Vec<F>,
blinding: Vec<F>,
left: Vec<F>,
right: Vec<F>,
out: Vec<F>,
}
impl<F> Witness<F> {
pub fn new(
values: Vec<F>,
blinding: Vec<F>,
left: Vec<F>,
right: Vec<F>,
out: Vec<F>,
) -> Option<Self> {
if values.len() != blinding.len() {
return None;
}
if left.len() != right.len() || right.len() != out.len() {
return None;
}
Some(Self {
values,
blinding,
left,
right,
out,
})
}
pub fn values(&self) -> &[F] {
&self.values
}
#[must_use]
pub fn is_satisfied(&self, circuit: &Circuit<F>) -> bool
where
F: Ring,
{
circuit.is_satisfied(&self.values, &self.left, &self.right)
}
pub fn claim<G: Space<F>>(&self, setup: &Setup<G>) -> Claim<G> {
Claim {
commitments: self
.values
.iter()
.zip(&self.blinding)
.map(|(value, blind)| {
setup.value_generator.clone() * value
+ &(setup.blinding_generator.clone() * blind)
})
.collect(),
}
}
}
pub struct Claim<G> {
pub commitments: Vec<G>,
}
impl<G: Write> Write for Claim<G> {
fn write(&self, buf: &mut impl BufMut) {
self.commitments.write(buf);
}
}
impl<G: EncodeSize> EncodeSize for Claim<G> {
fn encode_size(&self) -> usize {
self.commitments.encode_size()
}
}
#[allow(dead_code)]
#[derive(Clone)]
pub struct Proof<F, G> {
m_big: G,
o_big: G,
m_big_tilde: G,
t_big: [G; 5],
s_tilde: F,
t_x: F,
t_tilde_x: F,
p_big: G,
ipa_proof: ipa::Proof<F, G>,
}
impl<F: Write, G: Write> Write for Proof<F, G> {
fn write(&self, buf: &mut impl BufMut) {
self.m_big.write(buf);
self.o_big.write(buf);
self.m_big_tilde.write(buf);
for t in &self.t_big {
t.write(buf);
}
self.s_tilde.write(buf);
self.t_x.write(buf);
self.t_tilde_x.write(buf);
self.p_big.write(buf);
self.ipa_proof.write(buf);
}
}
impl<F: EncodeSize, G: EncodeSize> EncodeSize for Proof<F, G> {
fn encode_size(&self) -> usize {
self.m_big.encode_size()
+ self.o_big.encode_size()
+ self.m_big_tilde.encode_size()
+ self.t_big.iter().map(|t| t.encode_size()).sum::<usize>()
+ self.s_tilde.encode_size()
+ self.t_x.encode_size()
+ self.t_tilde_x.encode_size()
+ self.p_big.encode_size()
+ self.ipa_proof.encode_size()
}
}
impl<F: Read, G: Read> Read for Proof<F, G>
where
F::Cfg: Clone,
G::Cfg: Clone,
{
type Cfg = (usize, (G::Cfg, F::Cfg));
fn read_cfg(buf: &mut impl Buf, cfg @ (_, (g_cfg, f_cfg)): &Self::Cfg) -> Result<Self, Error> {
let m_big = G::read_cfg(buf, g_cfg)?;
let o_big = G::read_cfg(buf, g_cfg)?;
let m_big_tilde = G::read_cfg(buf, g_cfg)?;
let t_big = [
G::read_cfg(buf, g_cfg)?,
G::read_cfg(buf, g_cfg)?,
G::read_cfg(buf, g_cfg)?,
G::read_cfg(buf, g_cfg)?,
G::read_cfg(buf, g_cfg)?,
];
let s_tilde = F::read_cfg(buf, f_cfg)?;
let t_x = F::read_cfg(buf, f_cfg)?;
let t_tilde_x = F::read_cfg(buf, f_cfg)?;
let p_big = G::read_cfg(buf, g_cfg)?;
let ipa_proof = ipa::Proof::read_cfg(buf, cfg)?;
Ok(Self {
m_big,
o_big,
m_big_tilde,
t_big,
s_tilde,
t_x,
t_tilde_x,
p_big,
ipa_proof,
})
}
}
pub fn prove<F: Field + Encode + Random, G: CryptoGroup<Scalar = F> + Encode>(
rng: &mut impl CryptoRng,
transcript: &mut Transcript,
setup: &Setup<G>,
circuit: &Circuit<F>,
claim: &Claim<G>,
witness: &Witness<F>,
strategy: &impl Strategy,
) -> Option<Proof<F, G>> {
let l_tilde = (0..circuit.internal_vars)
.map(|_| F::random(&mut *rng))
.collect::<Vec<_>>();
let r_tilde = (0..circuit.internal_vars)
.map(|_| F::random(&mut *rng))
.collect::<Vec<_>>();
let m = F::random(&mut *rng);
let o_tilde = F::random(&mut *rng);
let m_tilde = F::random(&mut *rng);
let g_internal = &setup.ipa.g()[..circuit.internal_vars];
let h_internal = &setup.ipa.h()[..circuit.internal_vars];
let m_big = G::msm(g_internal, &witness.left, strategy)
+ &G::msm(h_internal, &witness.right, strategy)
+ &(setup.blinding_generator.clone() * &m);
let o_big =
G::msm(g_internal, &witness.out, strategy) + &(setup.blinding_generator.clone() * &o_tilde);
let m_big_tilde = G::msm(g_internal, &l_tilde, strategy)
+ &G::msm(h_internal, &r_tilde, strategy)
+ &(setup.blinding_generator.clone() * &m_tilde);
circuit.commit(transcript);
transcript.commit(claim.encode());
transcript.commit(m_big.encode());
transcript.commit(o_big.encode());
transcript.commit(m_big_tilde.encode());
let padded_vars = circuit.internal_vars.next_power_of_two();
let y = F::random(transcript.noise(b"y"));
let y_powers = powers(F::one(), &y).take(padded_vars).collect::<Vec<_>>();
let y_inv = y.inv();
let y_inv_powers = powers(F::one(), &y_inv)
.take(padded_vars)
.collect::<Vec<_>>();
let z = F::random(transcript.noise(b"z"));
let z_powers = powers(z.clone(), &z)
.take(circuit.weights.height())
.collect::<Vec<_>>();
let (kappa, theta, lambda, rho, omega) = {
let mut kappa = F::zero();
let mut theta = vec![F::zero(); circuit.committed_vars];
let mut lambda = vec![F::zero(); circuit.internal_vars];
let mut rho = vec![F::zero(); circuit.internal_vars];
let mut omega = vec![F::zero(); circuit.internal_vars];
let theta_start = 1;
let lambda_start = theta_start + circuit.committed_vars;
let rho_start = lambda_start + circuit.internal_vars;
let omega_start = rho_start + circuit.internal_vars;
for (&(i, j), w_ij) in &circuit.weights.weights {
let w_ij = w_ij.clone();
if j >= omega_start {
omega[j - omega_start] += &(w_ij * &z_powers[i]);
} else if j >= rho_start {
rho[j - rho_start] += &(w_ij * &z_powers[i]);
} else if j >= lambda_start {
lambda[j - lambda_start] += &(w_ij * &z_powers[i]);
} else if j >= theta_start {
theta[j - theta_start] += &(w_ij * &z_powers[i]);
} else {
kappa += &(w_ij * &z_powers[i]);
}
}
(kappa, theta, lambda, rho, omega)
};
let mut omega_minus_y = omega
.iter()
.cloned()
.zip(&y_powers)
.map(|(omega_i, y_i)| omega_i - y_i)
.collect::<Vec<_>>();
omega_minus_y.extend(
y_powers
.iter()
.skip(circuit.internal_vars)
.cloned()
.map(|y_i| -y_i),
);
let y_inv_rho = y_inv_powers
.iter()
.cloned()
.zip(&rho)
.map(|(y_inv_i, rho_i)| y_inv_i * rho_i)
.collect::<Vec<_>>();
let y_inv_lambda = y_inv_powers
.iter()
.cloned()
.zip(&lambda)
.map(|(y_inv_i, lambda_i)| y_inv_i * lambda_i)
.collect::<Vec<_>>();
let y_inv_omega_minus_y = y_inv_powers
.iter()
.cloned()
.zip(&omega_minus_y)
.map(|(y_inv_i, omega_minus_y_i)| y_inv_i * omega_minus_y_i)
.collect::<Vec<_>>();
let y_r = y_powers
.iter()
.cloned()
.zip(&witness.right)
.map(|(y_i, r_i)| y_i * r_i)
.collect::<Vec<_>>();
let y_r_tilde = y_powers
.iter()
.cloned()
.zip(&r_tilde)
.map(|(y_i, r_i)| y_i * r_i)
.collect::<Vec<_>>();
let delta_y_z = <F as Space<F>>::msm(&y_inv_rho, &lambda, strategy);
let t = {
let mut t = std::array::from_fn::<_, 6, _>(|_| F::zero());
for i in 0..circuit.internal_vars {
t[0] += &((witness.left[i].clone() + &y_inv_rho[i]) * &omega_minus_y[i]);
}
t[1] = delta_y_z - &kappa - &<F as Space<F>>::msm(&theta, &witness.values, strategy);
for i in 0..circuit.internal_vars {
t[2] += &(l_tilde[i].clone() * &omega_minus_y[i]);
t[2] += &(witness.out[i].clone() * &(y_r[i].clone() + &lambda[i]));
}
for i in 0..circuit.internal_vars {
t[3] += &(l_tilde[i].clone() * &(y_r[i].clone() + &lambda[i]));
t[3] += &((witness.left[i].clone() + &y_inv_rho[i]) * &y_r_tilde[i]);
}
t[4] = <F as Space<F>>::msm(&witness.out, &y_r_tilde, strategy);
t[5] = <F as Space<F>>::msm(&l_tilde, &y_r_tilde, strategy);
t
};
let t_tilde = std::array::from_fn::<_, 6, _>(|i| {
if i == 1 {
-<F as Space<F>>::msm(&theta, &witness.blinding, strategy)
} else {
F::random(&mut *rng)
}
});
let t_big = std::array::from_fn::<_, 5, _>(|i| {
let i = if i >= 1 { i + 1 } else { i };
setup.value_generator.clone() * &t[i] + &(setup.blinding_generator.clone() * &t_tilde[i])
});
let p_0 = G::msm(
&setup.ipa.h()[..padded_vars],
&y_inv_omega_minus_y,
strategy,
);
let h_internal = &setup.ipa.h()[..circuit.internal_vars];
let p_1 =
G::msm(g_internal, &y_inv_rho, strategy) + &G::msm(h_internal, &y_inv_lambda, strategy);
for t_big_i in &t_big {
transcript.commit(t_big_i.encode());
}
let x = F::random(transcript.noise(b"x"));
let x = powers(x.clone(), &x).take(6).collect::<Vec<_>>();
let s_tilde = m * &x[0] + &(o_tilde * &x[1]) + &(m_tilde * &x[2]);
let p = setup.blinding_generator.clone() * &(-s_tilde.clone())
+ &p_0
+ &((p_1 + &m_big) * &x[0])
+ &(o_big.clone() * &x[1])
+ &(m_big_tilde.clone() * &x[2]);
let t_x = <F as Space<F>>::msm(&t, &x, strategy);
let t_tilde_x = <F as Space<F>>::msm(&t_tilde, &x, strategy);
let ipa_claim = ipa::Claim {
commitment: p.clone(),
product: t_x.clone(),
y: y_inv,
log_len: padded_vars.ilog2().try_into().ok()?,
};
let mut f_x = (0..circuit.internal_vars)
.map(|i| {
(witness.left[i].clone() + &y_inv_rho[i]) * &x[0]
+ &(witness.out[i].clone() * &x[1])
+ &(l_tilde[i].clone() * &x[2])
})
.collect::<Vec<_>>();
f_x.resize(padded_vars, F::zero());
let mut g_x = (0..circuit.internal_vars)
.map(|i| {
(y_r[i].clone() + &lambda[i]) * &x[0]
+ &omega_minus_y[i]
+ &(y_r_tilde[i].clone() * &x[2])
})
.collect::<Vec<_>>();
g_x.extend_from_slice(&omega_minus_y[circuit.internal_vars..]);
let witness = ipa::Witness::new(f_x.into_iter().zip(g_x))?;
let ipa_proof = ipa::prove(transcript, &setup.ipa, &ipa_claim, witness, strategy)?;
Some(Proof {
m_big,
o_big,
m_big_tilde,
t_big,
s_tilde,
t_x,
t_tilde_x,
p_big: p,
ipa_proof,
})
}
pub fn verify<F: Field + Encode + Random, G: CryptoGroup<Scalar = F> + Encode>(
rng: &mut impl CryptoRng,
transcript: &mut Transcript,
setup: &Setup<Synthetic<F, G>>,
circuit: &Circuit<F>,
claim: &Claim<G>,
proof: Proof<F, G>,
strategy: &impl Strategy,
) -> Option<Synthetic<F, G>> {
let Proof {
m_big,
o_big,
m_big_tilde,
t_big,
s_tilde,
t_x,
t_tilde_x,
ipa_proof,
p_big: p,
} = proof;
if claim.commitments.len() != circuit.committed_vars {
return None;
}
circuit.commit(transcript);
transcript.commit(claim.encode());
transcript.commit(m_big.encode());
transcript.commit(o_big.encode());
transcript.commit(m_big_tilde.encode());
let padded_vars = circuit.internal_vars.next_power_of_two();
let y = F::random(transcript.noise(b"y"));
let y_powers = powers(F::one(), &y).take(padded_vars).collect::<Vec<_>>();
let y_inv = y.inv();
let y_inv_powers = powers(F::one(), &y_inv)
.take(padded_vars)
.collect::<Vec<_>>();
let z = F::random(transcript.noise(b"z"));
let z_powers = powers(z.clone(), &z)
.take(circuit.weights.height())
.collect::<Vec<_>>();
let (kappa, theta, lambda, rho, omega) = {
let mut kappa = F::zero();
let mut theta = vec![F::zero(); circuit.committed_vars];
let mut lambda = vec![F::zero(); circuit.internal_vars];
let mut rho = vec![F::zero(); circuit.internal_vars];
let mut omega = vec![F::zero(); circuit.internal_vars];
let theta_start = 1;
let lambda_start = theta_start + circuit.committed_vars;
let rho_start = lambda_start + circuit.internal_vars;
let omega_start = rho_start + circuit.internal_vars;
for (&(i, j), w_ij) in &circuit.weights.weights {
let w_ij = w_ij.clone();
if j >= omega_start {
omega[j - omega_start] += &(w_ij * &z_powers[i]);
} else if j >= rho_start {
rho[j - rho_start] += &(w_ij * &z_powers[i]);
} else if j >= lambda_start {
lambda[j - lambda_start] += &(w_ij * &z_powers[i]);
} else if j >= theta_start {
theta[j - theta_start] += &(w_ij * &z_powers[i]);
} else {
kappa += &(w_ij * &z_powers[i]);
}
}
(kappa, theta, lambda, rho, omega)
};
let mut omega_minus_y = omega
.iter()
.cloned()
.zip(&y_powers)
.map(|(omega_i, y_i)| omega_i - y_i)
.collect::<Vec<_>>();
omega_minus_y.extend(
y_powers
.iter()
.skip(circuit.internal_vars)
.cloned()
.map(|y_i| -y_i),
);
let y_inv_rho = y_inv_powers
.iter()
.cloned()
.zip(&rho)
.map(|(y_inv_i, rho_i)| y_inv_i * rho_i)
.collect::<Vec<_>>();
let y_inv_lambda = y_inv_powers
.iter()
.cloned()
.zip(&lambda)
.map(|(y_inv_i, lambda_i)| y_inv_i * lambda_i)
.collect::<Vec<_>>();
let y_inv_omega_minus_y = y_inv_powers
.iter()
.cloned()
.zip(&omega_minus_y)
.map(|(y_inv_i, omega_minus_y_i)| y_inv_i * omega_minus_y_i)
.collect::<Vec<_>>();
let delta_y_z = <F as Space<F>>::msm(&y_inv_rho, &lambda, strategy);
for t_big_i in &t_big {
transcript.commit(t_big_i.encode());
}
let x = F::random(transcript.noise(b"x"));
let x = powers(x.clone(), &x).take(6).collect::<Vec<_>>();
let ipa_g = setup.ipa.g();
let ipa_h = setup.ipa.h();
let value_generator = &setup.value_generator;
let blinding_generator = &setup.blinding_generator;
let t_check = Synthetic::msm(
&[value_generator.clone(), blinding_generator.clone()],
&[t_x.clone(), t_tilde_x],
&Sequential,
) - &(value_generator.clone() * &((-kappa + &delta_y_z) * &x[1]))
+ &(Synthetic::concrete(theta.iter().cloned().zip(claim.commitments.iter().cloned()))
* &x[1])
- &Synthetic::concrete(std::iter::once(&x[0]).chain(&x[2..]).cloned().zip(t_big));
let p_check = {
let p_0 = Synthetic::msm(&ipa_h[..padded_vars], &y_inv_omega_minus_y, &Sequential);
let p_1 = Synthetic::msm(&ipa_g[..circuit.internal_vars], &y_inv_rho, &Sequential)
+ &Synthetic::msm(&ipa_h[..circuit.internal_vars], &y_inv_lambda, &Sequential);
Synthetic::concrete([
(F::one(), p.clone()),
(-x[0].clone(), m_big),
(-x[1].clone(), o_big),
(-x[2].clone(), m_big_tilde),
]) - &p_0
- &(p_1 * &x[0])
+ &(blinding_generator.clone() * &s_tilde)
};
let ipa_claim = ipa::Claim {
commitment: p,
product: t_x,
y: y_inv,
log_len: padded_vars
.ilog2()
.try_into()
.expect("should be less than 2^256 rows"),
};
let ipa_check = ipa::verify(transcript, &setup.ipa, &ipa_claim, ipa_proof)?;
let final_check =
ipa_check + &(p_check * &F::random(&mut *rng)) + &(t_check * &F::random(&mut *rng));
Some(final_check)
}
#[commonware_macros::stability(ALPHA)]
#[cfg(any(test, feature = "fuzz"))]
pub mod fuzz {
use super::*;
use arbitrary::{Arbitrary, Unstructured};
use commonware_math::{
algebra::{Additive, Ring},
test::{F, G},
};
use commonware_parallel::Sequential;
use commonware_utils::test_rng;
use std::sync::OnceLock;
const NAMESPACE: &[u8] = b"_COMMONWARE_CRYPTOGRAPHY_ZK_BULLETPROOFS_CIRCUIT";
const TEST_SETUP_PAIRS: usize = 64;
pub(super) fn test_setup() -> &'static Setup<G> {
static TEST_SETUP: OnceLock<Setup<G>> = OnceLock::new();
TEST_SETUP.get_or_init(|| {
let count = 2 * TEST_SETUP_PAIRS + 3;
let gens = (1..=count)
.map(|i| G::generator() * &F::from(i as u64))
.collect::<Vec<_>>();
Setup::new(
ipa::Setup::new(
gens[2 * TEST_SETUP_PAIRS],
gens[..2 * TEST_SETUP_PAIRS]
.chunks_exact(2)
.map(|c| (c[0], c[1])),
),
gens[2 * TEST_SETUP_PAIRS + 1],
gens[2 * TEST_SETUP_PAIRS + 2],
)
})
}
fn quadratic_value(a: F, b: F, c: F, x: F) -> F {
a * &x * &x + &(b * &x) + &c
}
pub(super) fn quadratic_circuit(a: F, b: F, c: F) -> Circuit<F> {
let mut weights = SparseMatrix::default();
weights[(0, 1)] = F::one();
weights[(0, 3)] = -F::one();
weights[(1, 1)] = F::one();
weights[(1, 4)] = -F::one();
weights[(2, 0)] = c;
weights[(2, 1)] = b;
weights[(2, 2)] = -F::one();
weights[(2, 5)] = a;
Circuit::new(2, weights).expect("quadratic circuit layout should be valid")
}
pub struct Case {
circuit: Circuit<F>,
witness: Witness<F>,
}
impl Case {
fn is_satisfied(&self) -> bool {
self.circuit.is_satisfied(
&self.witness.values,
&self.witness.left,
&self.witness.right,
)
}
fn arbitrary(u: &mut Unstructured<'_>) -> arbitrary::Result<Self> {
let a = u.arbitrary::<F>()?;
let b = u.arbitrary::<F>()?;
let c = u.arbitrary::<F>()?;
let x = u.arbitrary::<F>()?;
let valid = u.arbitrary::<bool>()?;
let mut y = quadratic_value(a, b, c, x);
if !valid {
let mut tweak = u.arbitrary::<F>()?;
if tweak == F::zero() {
tweak = F::one()
}
y += &tweak;
}
let x_sq = x * &x;
let witness = Witness::new(
vec![x, y],
vec![u.arbitrary::<F>()?, u.arbitrary::<F>()?],
vec![x],
vec![x],
vec![x_sq],
)
.expect("quadratic witness should have matching vector lengths");
let circuit = quadratic_circuit(a, b, c);
let out = Self { circuit, witness };
assert_eq!(
out.is_satisfied(),
valid,
"quadratic case should match requested validity",
);
Ok(out)
}
}
pub enum Plan {
ProveAndVerify(Case),
ZkcConversion(crate::zk::circuit::fuzz::Plan),
}
impl<'a> Arbitrary<'a> for Plan {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
match u.int_in_range(0..=1)? {
0 => Ok(Self::ProveAndVerify(Case::arbitrary(u)?)),
1 => Ok(Self::ZkcConversion(u.arbitrary()?)),
_ => unreachable!("plan variant out of range"),
}
}
}
fn assert_verify_matches_satisfaction(case: &Case) {
let setup = test_setup();
let claim = case.witness.claim(setup);
let verified = prove_and_verify(setup, &case.circuit, &claim, &case.witness);
assert_eq!(verified, case.is_satisfied());
}
fn prove_and_verify(
setup: &Setup<G>,
circuit: &Circuit<F>,
claim: &Claim<G>,
witness: &Witness<F>,
) -> bool {
let mut rng = test_rng();
let mut prover_transcript = Transcript::new(NAMESPACE);
let Some(proof) = super::prove(
&mut rng,
&mut prover_transcript,
setup,
circuit,
claim,
witness,
&Sequential,
) else {
return false;
};
let mut verifier_transcript = Transcript::new(NAMESPACE);
setup
.eval(
|vs| {
verify(
&mut rng,
&mut verifier_transcript,
vs,
circuit,
claim,
proof,
&Sequential,
)
},
&Sequential,
)
.map(|g| g == G::zero())
.unwrap_or(false)
}
pub(super) fn assert_zkc_conversion_preserves_satisfaction(
plan: &crate::zk::circuit::fuzz::Plan,
u: &mut Unstructured<'_>,
) -> arbitrary::Result<()> {
let valued = plan.build();
let mut committed = Vec::new();
for i in 0..valued.circuit.witnesses {
if u.arbitrary()? {
committed.push(crate::zk::circuit::CircuitIdx::Witness(i));
}
}
let (circuit, witness) =
zkc_to_circuit_and_witness(Some(&mut test_rng()), valued, &committed);
let satisfied = witness.is_satisfied(&circuit);
assert_eq!(satisfied, plan.satisfied(), "plan: {plan:?}");
if satisfied {
let setup = test_setup();
assert!(
circuit.internal_vars() <= TEST_SETUP_PAIRS,
"circuit too large for test setup ({} > {TEST_SETUP_PAIRS}); plan: {plan:?}",
circuit.internal_vars()
);
let honest = witness.claim(setup);
assert!(
prove_and_verify(setup, &circuit, &honest, &witness),
"honest claim must verify; plan: {plan:?}"
);
if !committed.is_empty() {
let j = u.choose_index(committed.len())?;
let mut tampered = witness.claim(setup);
tampered.commitments[j] += setup.value_generator();
assert!(
!prove_and_verify(setup, &circuit, &tampered, &witness),
"tampering committed value {j} must break verification; plan: {plan:?}"
);
}
}
Ok(())
}
impl Plan {
pub fn run(self, u: &mut Unstructured<'_>) -> arbitrary::Result<()> {
match self {
Self::ProveAndVerify(case) => assert_verify_matches_satisfaction(&case),
Self::ZkcConversion(plan) => {
assert_zkc_conversion_preserves_satisfaction(&plan, u)?
}
}
Ok(())
}
}
}
#[cfg(test)]
mod test {
use super::{fuzz, prove, verify, Circuit, Setup, SparseMatrix, Witness};
use crate::{transcript::Transcript, zk::circuit as zk};
use commonware_codec::{Decode, Encode};
use commonware_invariants::minifuzz;
use commonware_math::{
algebra::{Additive, CryptoGroup, Ring},
test::{F, G},
};
use commonware_parallel::Sequential;
use commonware_utils::test_rng;
#[test]
fn test_zkc_conversion_preserves_satisfaction_minifuzz() {
minifuzz::test(|u| {
let plan = u.arbitrary::<zk::fuzz::Plan>()?;
fuzz::assert_zkc_conversion_preserves_satisfaction(&plan, u)
});
}
#[test]
fn test_zkc_conversion_preserves_committed_order() {
let (valued, _) = zk::build_with_values(|ctx| {
let a = zk::Var::witness(ctx, |_| F::from(1u64));
let b = zk::Var::witness(ctx, |_| F::from(2u64));
let c = a * &b;
c.assert_eq(&zk::Var::constant(ctx, F::from(2u64)));
Vec::new()
});
let (circuit, witness) = super::zkc_to_circuit_and_witness(
Some(&mut test_rng()),
valued,
&[
zk::CircuitIdx::Witness(1),
zk::CircuitIdx::Witness(0),
zk::CircuitIdx::Witness(1),
],
);
assert!(witness.is_satisfied(&circuit));
assert_eq!(
witness.values,
vec![F::from(2u64), F::from(1u64), F::from(2u64)]
);
}
#[test]
fn test_zkc_conversion_add_doubling_chain() {
const DEPTH: usize = 64;
let mut expected = F::one();
for _ in 0..DEPTH {
expected = expected + &expected;
}
let (valued, _) = zk::build_with_values(|ctx| {
let mut x = zk::Var::witness(ctx, |_| F::one());
for _ in 0..DEPTH {
x = x.clone() + &x;
}
x.assert_eq(&zk::Var::constant(ctx, expected));
Vec::new()
});
let (circuit, witness) = super::zkc_to_circuit_and_witness(
Some(&mut test_rng()),
valued,
&[zk::CircuitIdx::Witness(0)],
);
assert!(witness.is_satisfied(&circuit));
}
#[test]
fn test_random_r1cs_minifuzz() {
const N: usize = 2;
const M: usize = 4;
minifuzz::test(|u| {
let a = u.arbitrary::<[[F; N]; M]>()?;
let b = u.arbitrary::<[[F; N]; M]>()?;
let c = u.arbitrary::<[[F; N]; M]>()?;
let z = u.arbitrary::<[F; N]>()?;
let mut left = [F::zero(); M];
let mut right = [F::zero(); M];
let mut satisfied = true;
for i in 0..M {
let mut acc = F::zero();
for j in 0..N {
left[i] += &(a[i][j] * &z[j]);
right[i] += &(b[i][j] * &z[j]);
acc += &(c[i][j] * &z[j]);
}
satisfied = satisfied && acc == left[i] * &right[i];
}
let mut k = 0;
let mut weights = SparseMatrix::default();
for i in 0..M {
weights[(k, 1 + N + i)] = -F::one();
for j in 0..N {
weights[(k, 1 + j)] = a[i][j];
}
k += 1;
}
for i in 0..M {
weights[(k, 1 + N + M + i)] = -F::one();
for j in 0..N {
weights[(k, 1 + j)] = b[i][j];
}
k += 1;
}
for i in 0..M {
weights[(k, 1 + N + 2 * M + i)] = -F::one();
for j in 0..N {
weights[(k, 1 + j)] = c[i][j];
}
k += 1;
}
assert_eq!(
satisfied,
Circuit::new(N, weights)
.expect("should be able to make circuit")
.is_satisfied(&z, &left, &right)
);
Ok(())
});
}
#[test]
fn test_setup_roundtrip() {
let setup = fuzz::test_setup();
let encoded = setup.encode();
let decoded: Setup<G> = Setup::decode_cfg(encoded.clone(), &(setup.ipa.g().len(), ()))
.expect("setup should decode with its own length bound");
assert!(setup == &decoded);
assert_eq!(decoded.encode(), encoded);
}
#[test]
fn test_fuzz() {
minifuzz::test(|u| {
u.arbitrary::<fuzz::Plan>()?.run(u)?;
Ok(())
});
}
#[test]
fn verify_rejects_over_long_claim() {
let setup = fuzz::test_setup();
let a = F::one();
let b = F::one();
let c = F::one();
let x = F::from(3u8);
let y = a * &x * &x + &(b * &x) + &c;
let circuit = fuzz::quadratic_circuit(a, b, c);
let witness = Witness::new(
vec![x, y],
vec![F::zero(), F::zero()],
vec![x],
vec![x],
vec![x * &x],
)
.expect("witness vector lengths must be consistent");
let mut claim = witness.claim(setup);
claim.commitments.push(G::generator() * &F::from(9u8));
let mut rng = test_rng();
let mut prover_transcript = Transcript::new(b"verify-rejects-over-long-claim");
let proof = prove(
&mut rng,
&mut prover_transcript,
setup,
&circuit,
&claim,
&witness,
&Sequential,
)
.expect("prove still produces a proof against the malformed claim");
let mut verifier_transcript = Transcript::new(b"verify-rejects-over-long-claim");
let verified = setup.eval(
|vs| {
verify(
&mut rng,
&mut verifier_transcript,
vs,
&circuit,
&claim,
proof,
&Sequential,
)
},
&Sequential,
);
assert!(
verified.is_none(),
"verify must reject a claim whose commitment arity does not match the circuit"
);
}
}