use crate::constraint_system::{Constraint, Selector, WiredWitness, Witness};
use crate::permutation::Permutation;
use crate::plonkup::LookupTable;
use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use dusk_bls12_381::BlsScalar;
use hashbrown::HashMap;
#[derive(Debug)]
pub struct TurboComposer {
pub(crate) n: usize,
pub(crate) q_m: Vec<BlsScalar>,
pub(crate) q_l: Vec<BlsScalar>,
pub(crate) q_r: Vec<BlsScalar>,
pub(crate) q_o: Vec<BlsScalar>,
pub(crate) q_4: Vec<BlsScalar>,
pub(crate) q_c: Vec<BlsScalar>,
pub(crate) q_arith: Vec<BlsScalar>,
pub(crate) q_range: Vec<BlsScalar>,
pub(crate) q_logic: Vec<BlsScalar>,
pub(crate) q_fixed_group_add: Vec<BlsScalar>,
pub(crate) q_variable_group_add: Vec<BlsScalar>,
pub(crate) q_lookup: Vec<BlsScalar>,
pub(crate) public_inputs_sparse_store: BTreeMap<usize, BlsScalar>,
pub(crate) w_l: Vec<Witness>,
pub(crate) w_r: Vec<Witness>,
pub(crate) w_o: Vec<Witness>,
pub(crate) w_4: Vec<Witness>,
pub(crate) lookup_table: LookupTable,
pub(crate) witnesses: HashMap<Witness, BlsScalar>,
pub(crate) perm: Permutation,
}
impl TurboComposer {
pub const fn constant_zero() -> Witness {
Witness::new(0)
}
pub const fn gates(&self) -> usize {
self.n
}
pub unsafe fn evaluate_witness(&self, witness: &Witness) -> &BlsScalar {
&self.witnesses[witness]
}
pub(crate) fn to_dense_public_inputs(&self) -> Vec<BlsScalar> {
let mut pi = vec![BlsScalar::zero(); self.n];
self.public_inputs_sparse_store
.iter()
.for_each(|(pos, value)| {
pi[*pos] = *value;
});
pi
}
pub fn public_input_indexes(&self) -> Vec<usize> {
self.public_inputs_sparse_store
.keys()
.copied()
.collect::<Vec<usize>>()
}
}
impl Default for TurboComposer {
fn default() -> Self {
Self::new()
}
}
impl TurboComposer {
pub(crate) fn new() -> Self {
TurboComposer::with_size(0)
}
pub fn append_constant(&mut self, value: BlsScalar) -> Witness {
let witness = self.append_witness(value);
self.assert_equal_constant(witness, value, None);
witness
}
pub(crate) fn with_size(size: usize) -> Self {
let mut composer = TurboComposer {
n: 0,
q_m: Vec::with_capacity(size),
q_l: Vec::with_capacity(size),
q_r: Vec::with_capacity(size),
q_o: Vec::with_capacity(size),
q_c: Vec::with_capacity(size),
q_4: Vec::with_capacity(size),
q_arith: Vec::with_capacity(size),
q_range: Vec::with_capacity(size),
q_logic: Vec::with_capacity(size),
q_fixed_group_add: Vec::with_capacity(size),
q_variable_group_add: Vec::with_capacity(size),
q_lookup: Vec::with_capacity(size),
public_inputs_sparse_store: BTreeMap::new(),
w_l: Vec::with_capacity(size),
w_r: Vec::with_capacity(size),
w_o: Vec::with_capacity(size),
w_4: Vec::with_capacity(size),
lookup_table: LookupTable::new(),
witnesses: HashMap::with_capacity(size),
perm: Permutation::new(),
};
composer.append_constant(BlsScalar::zero());
composer.append_dummy_gates();
composer
}
pub fn append_witness<T: Into<BlsScalar>>(&mut self, scalar: T) -> Witness {
let scalar = scalar.into();
let var = self.perm.new_variable();
self.witnesses.insert(var, scalar);
var
}
pub fn append_public_witness<T: Into<BlsScalar>>(
&mut self,
scalar: T,
) -> Witness {
let scalar = scalar.into();
let witness = self.append_witness(scalar);
self.assert_equal_constant(witness, 0, Some(-scalar));
witness
}
pub fn append_gate(&mut self, s: Constraint) {
let a = s.witness(WiredWitness::A);
let b = s.witness(WiredWitness::B);
let o = s.witness(WiredWitness::O);
let d = s.witness(WiredWitness::D);
let s = Constraint::arithmetic(&s);
let q_m = *s.coeff(Selector::Multiplication);
let q_l = *s.coeff(Selector::Left);
let q_r = *s.coeff(Selector::Right);
let q_o = *s.coeff(Selector::Output);
let q_4 = *s.coeff(Selector::Fourth);
let q_c = *s.coeff(Selector::Constant);
let pi = *s.coeff(Selector::PublicInput);
let q_arith = *s.coeff(Selector::Arithmetic);
let q_range = *s.coeff(Selector::Range);
let q_logic = *s.coeff(Selector::Logic);
let q_fixed_group_add = *s.coeff(Selector::GroupAddFixedBase);
let q_variable_group_add = *s.coeff(Selector::GroupAddVariableBase);
let q_lookup = *s.coeff(Selector::Lookup);
self.w_l.push(a);
self.w_r.push(b);
self.w_o.push(o);
self.w_4.push(d);
self.q_m.push(q_m);
self.q_l.push(q_l);
self.q_r.push(q_r);
self.q_o.push(q_o);
self.q_4.push(q_4);
self.q_c.push(q_c);
self.q_arith.push(q_arith);
self.q_range.push(q_range);
self.q_logic.push(q_logic);
self.q_fixed_group_add.push(q_fixed_group_add);
self.q_variable_group_add.push(q_variable_group_add);
self.q_lookup.push(q_lookup);
if s.has_public_input() {
self.public_inputs_sparse_store.insert(self.n, pi);
}
self.perm.add_variables_to_map(a, b, o, d, self.n);
self.n += 1;
}
pub fn assert_equal_constant<C: Into<BlsScalar>>(
&mut self,
a: Witness,
constant: C,
pi: Option<BlsScalar>,
) {
let constant = constant.into();
let constraint = Constraint::new().left(1).constant(-constant).a(a);
let constraint = match pi {
Some(pi) => constraint.public(pi),
None => constraint,
};
self.append_gate(constraint);
}
pub fn assert_equal(&mut self, a: Witness, b: Witness) {
let constraint =
Constraint::new().left(1).right(-BlsScalar::one()).a(a).b(b);
self.append_gate(constraint);
}
pub fn component_select(
&mut self,
bit: Witness,
a: Witness,
b: Witness,
) -> Witness {
debug_assert!(
self.witnesses[&bit] == BlsScalar::one()
|| self.witnesses[&bit] == BlsScalar::zero()
);
let constraint = Constraint::new().mult(1).a(bit).b(a);
let bit_times_a = self.gate_mul(constraint);
let constraint =
Constraint::new().left(-BlsScalar::one()).constant(1).a(bit);
let one_min_bit = self.gate_add(constraint);
let constraint = Constraint::new().mult(1).a(one_min_bit).b(b);
let one_min_bit_b = self.gate_mul(constraint);
let constraint = Constraint::new()
.left(1)
.right(1)
.a(one_min_bit_b)
.b(bit_times_a);
self.gate_add(constraint)
}
pub fn component_select_zero(
&mut self,
bit: Witness,
value: Witness,
) -> Witness {
debug_assert!(
self.witnesses[&bit] == BlsScalar::one()
|| self.witnesses[&bit] == BlsScalar::zero()
);
let constraint = Constraint::new().mult(1).a(bit).b(value);
self.gate_mul(constraint)
}
pub fn component_select_one(
&mut self,
bit: Witness,
value: Witness,
) -> Witness {
debug_assert!(
self.witnesses[&bit] == BlsScalar::one()
|| self.witnesses[&bit] == BlsScalar::zero()
);
let b = self.witnesses[&bit];
let v = self.witnesses[&value];
let f_x = BlsScalar::one() - b + (b * v);
let f_x = self.append_witness(f_x);
let constraint = Constraint::new()
.mult(1)
.left(-BlsScalar::one())
.output(-BlsScalar::one())
.constant(1)
.a(bit)
.b(value)
.o(f_x);
self.append_gate(constraint);
f_x
}
pub fn component_decomposition<const N: usize>(
&mut self,
scalar: Witness,
) -> [Witness; N] {
assert!(0 < N && N <= 256);
let mut decomposition = [Self::constant_zero(); N];
let acc = Self::constant_zero();
let acc = self.witnesses[&scalar]
.to_bits()
.iter()
.enumerate()
.zip(decomposition.iter_mut())
.fold(acc, |acc, ((i, w), d)| {
*d = self.append_witness(BlsScalar::from(*w as u64));
self.component_boolean(*d);
let constraint = Constraint::new()
.left(BlsScalar::pow_of_2(i as u64))
.right(1)
.a(*d)
.b(acc);
self.gate_add(constraint)
});
self.assert_equal(acc, scalar);
decomposition
}
pub fn append_dummy_gates(&mut self) {
self.q_m.push(BlsScalar::from(1));
self.q_l.push(BlsScalar::from(2));
self.q_r.push(BlsScalar::from(3));
self.q_o.push(BlsScalar::from(4));
self.q_c.push(BlsScalar::from(4));
self.q_4.push(BlsScalar::one());
self.q_arith.push(BlsScalar::one());
self.q_range.push(BlsScalar::zero());
self.q_logic.push(BlsScalar::zero());
self.q_fixed_group_add.push(BlsScalar::zero());
self.q_variable_group_add.push(BlsScalar::zero());
self.q_lookup.push(BlsScalar::one());
let var_six = self.append_witness(BlsScalar::from(6));
let var_one = self.append_witness(BlsScalar::from(1));
let var_seven = self.append_witness(BlsScalar::from(7));
let var_min_twenty = self.append_witness(-BlsScalar::from(20));
self.w_l.push(var_six);
self.w_r.push(var_seven);
self.w_o.push(var_min_twenty);
self.w_4.push(var_one);
self.perm.add_variables_to_map(
var_six,
var_seven,
var_min_twenty,
var_one,
self.n,
);
self.n += 1;
self.q_m.push(BlsScalar::from(1));
self.q_l.push(BlsScalar::from(1));
self.q_r.push(BlsScalar::from(1));
self.q_o.push(BlsScalar::from(1));
self.q_c.push(BlsScalar::from(127));
self.q_4.push(BlsScalar::zero());
self.q_arith.push(BlsScalar::one());
self.q_range.push(BlsScalar::zero());
self.q_logic.push(BlsScalar::zero());
self.q_fixed_group_add.push(BlsScalar::zero());
self.q_variable_group_add.push(BlsScalar::zero());
self.q_lookup.push(BlsScalar::one());
self.w_l.push(var_min_twenty);
self.w_r.push(var_six);
self.w_o.push(var_seven);
self.w_4.push(Self::constant_zero());
self.perm.add_variables_to_map(
var_min_twenty,
var_six,
var_seven,
Self::constant_zero(),
self.n,
);
self.lookup_table.0.insert(
0,
[
BlsScalar::from(6),
BlsScalar::from(7),
-BlsScalar::from(20),
BlsScalar::from(1),
],
);
self.lookup_table.0.insert(
0,
[
-BlsScalar::from(20),
BlsScalar::from(6),
BlsScalar::from(7),
BlsScalar::from(0),
],
);
self.lookup_table.0.insert(
0,
[
BlsScalar::from(3),
BlsScalar::from(1),
BlsScalar::from(4),
BlsScalar::from(9),
],
);
self.n += 1;
}
pub(crate) fn append_output_witness(&mut self, s: Constraint) -> Witness {
let a = s.witness(WiredWitness::A);
let b = s.witness(WiredWitness::B);
let d = s.witness(WiredWitness::D);
let a = self.witnesses[&a];
let b = self.witnesses[&b];
let d = self.witnesses[&d];
let qm = s.coeff(Selector::Multiplication);
let ql = s.coeff(Selector::Left);
let qr = s.coeff(Selector::Right);
let qd = s.coeff(Selector::Fourth);
let qc = s.coeff(Selector::Constant);
let pi = s.coeff(Selector::PublicInput);
let x = qm * a * b + ql * a + qr * b + qd * d + qc + pi;
let y = s.coeff(Selector::Output);
let y: Option<BlsScalar> = y.invert().into();
let y = -y.expect("Inconsistent internal usage of `Constraint::evaluate_output`: Output selector should not be zero");
let o = x * y;
self.append_witness(o)
}
#[cfg(feature = "trace")]
#[allow(dead_code)]
pub(crate) fn check_circuit_satisfied(&self) {
let w_l: Vec<&BlsScalar> = self
.w_l
.iter()
.map(|w_l_i| self.witnesses.get(w_l_i).unwrap())
.collect();
let w_r: Vec<&BlsScalar> = self
.w_r
.iter()
.map(|w_r_i| self.witnesses.get(w_r_i).unwrap())
.collect();
let w_o: Vec<&BlsScalar> = self
.w_o
.iter()
.map(|w_o_i| self.witnesses.get(w_o_i).unwrap())
.collect();
let w_4: Vec<&BlsScalar> = self
.w_4
.iter()
.map(|w_4_i| self.witnesses.get(w_4_i).unwrap())
.collect();
let delta = |f: BlsScalar| -> BlsScalar {
let f_1 = f - BlsScalar::one();
let f_2 = f - BlsScalar::from(2);
let f_3 = f - BlsScalar::from(3);
f * f_1 * f_2 * f_3
};
let pi_vec = self.to_dense_public_inputs();
let four = BlsScalar::from(4);
for i in 0..self.n {
let qm = self.q_m[i];
let ql = self.q_l[i];
let qr = self.q_r[i];
let qo = self.q_o[i];
let qc = self.q_c[i];
let q4 = self.q_4[i];
let qarith = self.q_arith[i];
let qrange = self.q_range[i];
let qlogic = self.q_logic[i];
let qfixed = self.q_fixed_group_add[i];
let qvar = self.q_variable_group_add[i];
let pi = pi_vec[i];
let a = w_l[i];
let a_next = w_l[(i + 1) % self.n];
let b = w_r[i];
let b_next = w_r[(i + 1) % self.n];
let c = w_o[i];
let d = w_4[i];
let d_next = w_4[(i + 1) % self.n];
#[cfg(all(feature = "trace-print", feature = "std"))]
std::println!(
"--------------------------------------------\n
#Gate Index = {}
#Constraint Polynomials:\n
- qm -> {:?}\n
- ql -> {:?}\n
- qr -> {:?}\n
- q4 -> {:?}\n
- qo -> {:?}\n
- qc -> {:?}\n
- q_arith -> {:?}\n
- q_range -> {:?}\n
- q_logic -> {:?}\n
- q_fixed_group_add -> {:?}\n
- q_variable_group_add -> {:?}\n
# Witness polynomials:\n
- w_l -> {:?}\n
- w_r -> {:?}\n
- w_o -> {:?}\n
- w_4 -> {:?}\n",
i,
qm,
ql,
qr,
q4,
qo,
qc,
qarith,
qrange,
qlogic,
qfixed,
qvar,
a,
b,
c,
d
);
let k = qarith
* ((qm * a * b)
+ (ql * a)
+ (qr * b)
+ (qo * c)
+ (q4 * d)
+ pi
+ qc)
+ qlogic
* (((delta(a_next - four * a) - delta(b_next - four * b))
* c)
+ delta(a_next - four * a)
+ delta(b_next - four * b)
+ delta(d_next - four * d)
+ match (
qlogic == BlsScalar::one(),
qlogic == -BlsScalar::one(),
) {
(true, false) => (a & b) - d,
(false, true) => (a ^ b) - d,
(false, false) => BlsScalar::zero(),
_ => unreachable!(),
})
+ qrange
* (delta(c - four * d)
+ delta(b - four * c)
+ delta(a - four * b)
+ delta(d_next - four * a));
assert_eq!(k, BlsScalar::zero(), "Check failed at gate {}", i,);
}
}
pub fn append_plonkup_gate(
&mut self,
a: Witness,
b: Witness,
c: Witness,
d: Witness,
pi: Option<BlsScalar>,
) -> Witness {
self.w_l.push(a);
self.w_r.push(b);
self.w_o.push(c);
self.w_4.push(d);
self.q_l.push(BlsScalar::zero());
self.q_r.push(BlsScalar::zero());
self.q_o.push(BlsScalar::zero());
self.q_c.push(BlsScalar::zero());
self.q_4.push(BlsScalar::zero());
self.q_arith.push(BlsScalar::zero());
self.q_m.push(BlsScalar::zero());
self.q_range.push(BlsScalar::zero());
self.q_logic.push(BlsScalar::zero());
self.q_fixed_group_add.push(BlsScalar::zero());
self.q_variable_group_add.push(BlsScalar::zero());
self.q_lookup.push(BlsScalar::one());
if let Some(pi) = pi {
debug_assert!(self.public_inputs_sparse_store.get(&self.n).is_none(), "The invariant of already having a PI inserted for this position should never exist");
self.public_inputs_sparse_store.insert(self.n, pi);
}
self.perm.add_variables_to_map(a, b, c, d, self.n);
self.n += 1;
c
}
pub fn append_plonkup_table(&mut self, table: &LookupTable) {
table.0.iter().for_each(|k| self.lookup_table.0.push(*k))
}
}
#[cfg(feature = "std")]
#[cfg(test)]
mod tests {
use super::*;
use crate::commitment_scheme::PublicParameters;
use crate::constraint_system::helper::*;
use crate::error::Error;
use crate::proof_system::{Prover, Verifier};
use rand_core::OsRng;
#[test]
fn test_initial_gates() {
let composer: TurboComposer = TurboComposer::new();
assert_eq!(3, composer.gates())
}
#[allow(unused_variables)]
#[test]
fn test_minimal_circuit() {
let res = gadget_tester(
|composer| {
composer.append_dummy_gates();
},
200,
);
assert!(res.is_ok());
}
#[test]
fn test_component_select() {
let res = gadget_tester(
|composer| {
let bit_1 = composer.append_witness(BlsScalar::one());
let bit_0 = TurboComposer::constant_zero();
let choice_a = composer.append_witness(BlsScalar::from(10u64));
let choice_b = composer.append_witness(BlsScalar::from(20u64));
let choice =
composer.component_select(bit_1, choice_a, choice_b);
composer.assert_equal(choice, choice_a);
let choice =
composer.component_select(bit_0, choice_a, choice_b);
composer.assert_equal(choice, choice_b);
},
32,
);
assert!(res.is_ok());
}
#[test]
fn test_gadget() {
let mut t = LookupTable::new();
t.insert_special_row(
BlsScalar::from(12),
BlsScalar::from(12),
BlsScalar::from(12),
BlsScalar::from(12),
);
t.insert_special_row(
BlsScalar::from(3),
BlsScalar::from(0),
BlsScalar::from(12),
BlsScalar::from(341),
);
t.insert_special_row(
BlsScalar::from(341),
BlsScalar::from(341),
BlsScalar::from(10),
BlsScalar::from(10),
);
let res = gadget_plonkup_tester(
|composer| {
let bit_1 = composer.append_witness(BlsScalar::one());
let bit_0 = TurboComposer::constant_zero();
let choice_a = composer.append_witness(BlsScalar::from(10u64));
let choice_b = composer.append_witness(BlsScalar::from(20u64));
let choice =
composer.component_select(bit_1, choice_a, choice_b);
composer.assert_equal(choice, choice_a);
let choice =
composer.component_select(bit_0, choice_a, choice_b);
composer.assert_equal(choice, choice_b);
},
65,
t,
);
assert!(res.is_ok());
}
#[test]
#[should_panic]
fn test_gadget_fail() {
let mut t = LookupTable::new();
t.insert_special_row(
BlsScalar::from(12),
BlsScalar::from(12),
BlsScalar::from(12),
BlsScalar::from(12),
);
let res = gadget_plonkup_tester(
|composer| {
let twelve = composer.append_constant(BlsScalar::from(12));
let three = composer.append_constant(BlsScalar::from(3));
composer
.append_plonkup_gate(twelve, twelve, twelve, three, None);
},
65,
t,
);
assert!(res.is_err());
}
#[test]
fn test_multiple_proofs() {
let public_parameters =
PublicParameters::setup(2 * 30, &mut OsRng).unwrap();
let mut prover = Prover::new(b"demo");
dummy_gadget(10, prover.composer_mut());
let (ck, _) = public_parameters.trim(2 * 20).unwrap();
prover.preprocess(&ck).unwrap();
let public_inputs = prover.cs.to_dense_public_inputs();
let mut proofs = Vec::new();
for _ in 0..3 {
proofs.push(prover.prove(&ck).unwrap());
dummy_gadget(10, prover.composer_mut());
}
let mut verifier = Verifier::new(b"demo");
dummy_gadget(10, verifier.composer_mut());
let (ck, vk) = public_parameters.trim(2 * 20).unwrap();
verifier.preprocess(&ck).unwrap();
for proof in proofs {
assert!(verifier.verify(&proof, &vk, &public_inputs).is_ok());
}
}
#[test]
fn test_plonkup_full() {
let public_parameters =
PublicParameters::setup(2 * 70, &mut OsRng).unwrap();
let mut prover = Prover::new(b"test");
prover.cs.lookup_table.insert_multi_mul(0, 3);
prover.key_transcript(b"key", b"additional seed information");
let output = prover.cs.lookup_table.lookup(
BlsScalar::from(2),
BlsScalar::from(3),
BlsScalar::one(),
);
let two = prover.cs.append_constant(BlsScalar::from(2));
let three = prover.cs.append_constant(BlsScalar::from(3));
let result = prover.cs.append_constant(output.unwrap());
let one = prover.cs.append_constant(BlsScalar::one());
prover.cs.append_plonkup_gate(two, three, result, one, None);
prover.cs.append_plonkup_gate(two, three, result, one, None);
prover.cs.append_plonkup_gate(two, three, result, one, None);
prover.cs.append_plonkup_gate(two, three, result, one, None);
prover.cs.append_plonkup_gate(two, three, result, one, None);
let constraint = Constraint::new().left(1).right(1).a(two).b(three);
prover.cs.gate_add(constraint);
let (ck, _) = public_parameters.trim(2 * 70).unwrap();
prover.preprocess(&ck).unwrap();
let public_inputs = prover.cs.to_dense_public_inputs();
prover.prove(&ck).unwrap();
drop(public_inputs);
}
#[test]
fn test_plonkup_proof() -> Result<(), Error> {
let public_parameters = PublicParameters::setup(1 << 8, &mut OsRng)?;
let mut prover = Prover::new(b"test");
let mut verifier = Verifier::new(b"test");
dummy_gadget_plonkup(4, prover.composer_mut());
prover.cs.lookup_table.insert_multi_mul(0, 3);
dummy_gadget_plonkup(4, verifier.composer_mut());
verifier.cs.lookup_table.insert_multi_mul(0, 3);
let (ck, vk) = public_parameters.trim(1 << 7)?;
prover.preprocess(&ck)?;
verifier.preprocess(&ck)?;
let public_inputs = prover.cs.to_dense_public_inputs();
let proof = prover.prove(&ck)?;
assert!(verifier.verify(&proof, &vk, &public_inputs).is_ok());
Ok(())
}
}