diff --git a/Cargo.toml b/Cargo.toml index 0207849..b631cd5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ resolver = "2" members = [ # "evm-vrfier", -# "pasta-tree", + "pasta-tree", "w3f-plonk-common", "w3f-ring-proof", # "w3f-ring-vrf-snark", @@ -20,7 +20,7 @@ ark-ff = { version = "0.6", default-features = false } ark-ec = { version = "0.6", default-features = false } ark-poly = { version = "0.6", default-features = false } ark-serialize = { version = "0.6", default-features = false, features = ["derive"] } -w3f-pcs = { version = "0.0.7", default-features = false } +w3f-pcs = { version = "0.0.6", git = "https://github.com/paritytech/fflonk/", branch = "ipa-pcs", default-features = false } w3f-plonk-common = { version = "0.0.10", path = "w3f-plonk-common", default-features = false } rayon = { version = "1", default-features = false } ark-transcript = { version = "0.0.6", default-features = false } diff --git a/pasta-tree/Cargo.toml b/pasta-tree/Cargo.toml index 0701c25..3bb1b6b 100644 --- a/pasta-tree/Cargo.toml +++ b/pasta-tree/Cargo.toml @@ -11,7 +11,7 @@ repository.workspace = true [dependencies] w3f-pcs.workspace = true w3f-plonk-common.workspace = true -w3f-ring-proof = { path = "../w3f-ring-proof", default-features = false } +#w3f-ring-proof = { path = "../w3f-ring-proof", default-features = false } ark-pallas = { version = "0.6", default-features = false, features = ["curve"] } ark-vesta = { version = "0.6", default-features = false } ark-transcript.workspace = true @@ -23,7 +23,9 @@ ark-serialize.workspace = true rayon = { workspace = true, optional = true } [dev-dependencies] +num-format = "0.4" ark-bls12-381.workspace = true +ark-ed-on-bls12-381-bandersnatch.workspace = true criterion.workspace = true [features] @@ -36,7 +38,7 @@ std = [ "ark-serialize/std", "w3f-pcs/std", "w3f-plonk-common/std", - "w3f-ring-proof/std", +# "w3f-ring-proof/std", ] parallel = [ "std", @@ -47,7 +49,7 @@ parallel = [ "ark-poly/parallel", "w3f-pcs/parallel", "w3f-plonk-common/parallel", - "w3f-ring-proof/parallel", +# "w3f-ring-proof/parallel", ] print-trace = ["ark-std/print-trace", ] asm = ["w3f-pcs/asm"] \ No newline at end of file diff --git a/pasta-tree/src/auth_path/blinded.rs b/pasta-tree/src/auth_path/blinded.rs index 041b4e3..90a1f8e 100644 --- a/pasta-tree/src/auth_path/blinded.rs +++ b/pasta-tree/src/auth_path/blinded.rs @@ -1,7 +1,8 @@ use crate::auth_path::node::LevelWitnessWithBlinding; -use crate::{CycleParams, CycleSide}; +use crate::{AffinePoint, CircuitParams, CurveModel, CycleParams, CycleSide, ProjectivePoint}; use ark_ec::CurveGroup; use ark_ff::PrimeField; +use w3f_pcs::pcs::ipa::hiding::HidingIpa; pub struct AuthenticationPathWithBlinding { pub(crate) c0_path: Vec>, @@ -14,41 +15,36 @@ pub struct BlindedAuthenticationPath { pub(crate) c1_path: Vec, } -impl AuthenticationPathWithBlinding +impl AuthenticationPathWithBlinding, ProjectivePoint> where - F0: PrimeField, - F1: PrimeField, - C0: CurveGroup, - C1: CurveGroup, + C0: CurveModel, + C1: CurveModel, { pub(crate) fn apply_bfs( &self, - params: &CycleParams, - ) -> BlindedAuthenticationPath { + c0_pcs_params: &HidingIpa>, + c1_pcs_params: &HidingIpa>, + ) -> BlindedAuthenticationPath, ProjectivePoint> { let c0_path = self .c0_path .iter() - .map(|c0_level| { - c0_level - .blinded_path_node(¶ms.c0_params.pcs_params) - .unwrap() - }) + .map(|c0_level| c0_level.blinded_path_node(c0_pcs_params).unwrap()) .collect(); let c1_path = self .c1_path .iter() - .map(|c1_level| { - c1_level - .blinded_path_node(¶ms.c1_params.pcs_params) - .unwrap() - }) + .map(|c1_level| c1_level.blinded_path_node(c1_pcs_params).unwrap()) .collect(); BlindedAuthenticationPath { c0_path, c1_path } } - pub fn compute_root( + pub fn compute_root( &self, - params: &CycleParams, - ) -> Result, ()> { + params: &CycleParams, + ) -> Result, AffinePoint>, ()> + where + P0: CircuitParams, C1>, + P1: CircuitParams, C0>, + { let mut c0_path_iter = self.c0_path.iter(); let c0_nodes = c0_path_iter.next().unwrap(); let mut parent_on_c1 = c0_nodes.compute_parent(¶ms.c1_params)?; diff --git a/pasta-tree/src/auth_path/node.rs b/pasta-tree/src/auth_path/node.rs index 27c13fb..ea09136 100644 --- a/pasta-tree/src/auth_path/node.rs +++ b/pasta-tree/src/auth_path/node.rs @@ -1,4 +1,4 @@ -use crate::CycleSideParams; +use crate::{AffinePoint, CircuitParams, CurveModel, CycleSideParams, ProjectivePoint}; use ark_ec::{AffineRepr, CurveGroup}; use ark_ff::{PrimeField, Zero}; use ark_std::UniformRand; @@ -15,8 +15,8 @@ pub struct LevelWitness { pub(crate) path_node_idx: usize, } -impl LevelWitness { - pub fn new(siblings: Vec, path_node_idx: usize) -> Result { +impl LevelWitness> { + pub fn new(siblings: Vec>, path_node_idx: usize) -> Result { debug_assert!(path_node_idx < siblings.len()); (path_node_idx < siblings.len()).then_some(()).ok_or(())?; Ok(Self { @@ -29,7 +29,7 @@ impl LevelWitness { self.siblings.iter().map(|p| p.x()).flatten().collect() } - pub fn path_node(&self) -> G { + pub fn path_node(&self) -> AffinePoint { self.siblings[self.path_node_idx] } @@ -37,7 +37,7 @@ impl LevelWitness { &self, self_bf: G::ScalarField, parent_bf: G::BaseField, - ) -> LevelWitnessWithBlinding { + ) -> LevelWitnessWithBlinding> { LevelWitnessWithBlinding { level_witness: self.clone(), bf: self_bf, @@ -49,33 +49,35 @@ impl LevelWitness { &self, parent_bf: G::BaseField, rng: &mut R, - ) -> LevelWitnessWithBlinding { + ) -> LevelWitnessWithBlinding> { self.with_blinding(G::ScalarField::rand(rng), parent_bf) } - pub fn compute_parent>( - &self, - params: &CycleSideParams, - ) -> Result + pub fn compute_parent(&self, params: &CycleSideParams) -> Result where G::BaseField: PrimeField, + C: CurveGroup, + P: CircuitParams, { self.compute_parent_with_bf(params, C::ScalarField::zero()) } - fn compute_parent_with_bf>( + fn compute_parent_with_bf( &self, - params: &CycleSideParams, + params: &CycleSideParams, bf: C::ScalarField, ) -> Result where G::BaseField: PrimeField, + C: CurveGroup, + P: CircuitParams, { - params.commit_nodes(&self.siblings, bf) + params.commit_tree_nodes(&self.x_coords(), bf).map(|c| c.0) } } /// NB! It is not "blinded", meaning that the blinding factor hasn't been applied. +#[derive(Clone, Debug)] pub struct LevelWitnessWithBlinding { pub(crate) level_witness: LevelWitness, /// the verifier gets `Ci' = siblings[i] + bf.H` @@ -88,8 +90,11 @@ pub struct LevelWitnessWithBlinding { pub(crate) parent_bf: G::BaseField, // = C::ScalarField } -impl LevelWitnessWithBlinding { - pub(crate) fn blinded_path_node(&self, ipa_pcs: &HidingIpa) -> Result { +impl LevelWitnessWithBlinding> { + pub(crate) fn blinded_path_node( + &self, + ipa_pcs: &HidingIpa>, + ) -> Result, ()> { let blinded_path_node = ipa_pcs.reblind( self.level_witness.path_node(), G::ScalarField::zero(), @@ -98,12 +103,14 @@ impl LevelWitnessWithBlinding { Ok(blinded_path_node.0) } - pub(crate) fn compute_parent>( + pub(crate) fn compute_parent( &self, - params: &CycleSideParams, + params: &CycleSideParams, ) -> Result where G::BaseField: PrimeField, + C: CurveGroup, + P: CircuitParams, { self.level_witness .compute_parent_with_bf(params, self.parent_bf) diff --git a/pasta-tree/src/auth_path/path.rs b/pasta-tree/src/auth_path/path.rs index d23d388..b1d8619 100644 --- a/pasta-tree/src/auth_path/path.rs +++ b/pasta-tree/src/auth_path/path.rs @@ -1,8 +1,9 @@ use crate::auth_path::blinded::AuthenticationPathWithBlinding; use crate::auth_path::node::LevelWitness; -use crate::{CycleParams, CycleSide}; +use crate::{AffinePoint, CircuitParams, CurveModel, CycleParams, CycleSide, ProjectivePoint}; use ark_ec::CurveGroup; -use ark_ff::PrimeField; +use ark_ff::UniformRand; +use ark_ff::{PrimeField, Zero}; use ark_std::rand::Rng; /// A non-hiding authentication path from a leaf to the root, split between the curves of the cycle. @@ -12,6 +13,7 @@ use ark_std::rand::Rng; /// `path_0[0]` contains the leaf (with its siblings). /// `commit(path_0[k].siblings) = path_1[k].siblings[path_1[k].i]`, if `path_1[k]` exists, /// otherwise it's the root. +#[derive(Clone, Debug)] pub struct AuthenticationPath { /// Nodes on the `C0` curve. pub c0_path: Vec>, @@ -19,27 +21,28 @@ pub struct AuthenticationPath { pub c1_path: Vec>, } -impl AuthenticationPath +impl AuthenticationPath, ProjectivePoint> where - F0: PrimeField, - F1: PrimeField, - C0: CurveGroup, - C1: CurveGroup, + C0: CurveModel, + C1: CurveModel, { - pub fn with_blinding(&self, rng: &mut R) -> AuthenticationPathWithBlinding { + pub fn with_blinding( + &self, + rng: &mut R, + ) -> AuthenticationPathWithBlinding, ProjectivePoint> { let mut path_0 = Vec::with_capacity(self.c0_path.len()); let mut path_1 = Vec::with_capacity(self.c1_path.len()); let mut c0_path_iter = self.c0_path.iter(); let mut c0_nodes = c0_path_iter.next().unwrap(); // shouldn't be empty - let mut c0_bf = C0::ScalarField::rand(rng); + let mut c0_bf = C0::ScalarField::from(u128::rand(rng)); for c1_nodes in self.c1_path.iter() { - let c1_bf = C1::ScalarField::rand(rng); + let c1_bf = C1::ScalarField::from(u128::rand(rng)); path_0.push(c0_nodes.with_blinding(c0_bf, c1_bf)); match c0_path_iter.next() { Some(c0_nodes_) => { c0_nodes = c0_nodes_; - c0_bf = C0::ScalarField::rand(rng); + c0_bf = C0::ScalarField::from(u128::rand(rng)); path_1.push(c1_nodes.with_blinding(c1_bf, c0_bf)); } None => { @@ -65,14 +68,18 @@ where } } - pub fn get_leaf(&self) -> C0::Affine { + pub fn get_leaf(&self) -> AffinePoint { self.c0_path[0].path_node() } - pub fn compute_root( + pub fn compute_root( &self, - params: &CycleParams, - ) -> Result, ()> { + params: &CycleParams, + ) -> Result, AffinePoint>, ()> + where + P0: CircuitParams, C1>, + P1: CircuitParams, C0>, + { let mut c0_path_iter = self.c0_path.iter(); let c0_nodes = c0_path_iter.next().unwrap(); // shouldn't be empty let mut parent_on_c1 = c0_nodes.compute_parent(¶ms.c1_params)?; @@ -100,6 +107,7 @@ where #[cfg(test)] mod tests { use super::*; + use crate::circuit_fat::params::PiopParams; use crate::tests::random_path; use ark_std::test_rng; @@ -108,8 +116,12 @@ mod tests { let rng = &mut test_rng(); let domain_size = 2usize.pow(9); - let params = - CycleParams::::setup(domain_size, rng); + let params = CycleParams::< + ark_pallas::PallasConfig, + ark_vesta::VestaConfig, + PiopParams, + PiopParams, + >::setup(domain_size, rng); let (leaf, path, root) = random_path(¶ms, 2, rng); diff --git a/pasta-tree/src/circuit_fat/mod.rs b/pasta-tree/src/circuit_fat/mod.rs new file mode 100644 index 0000000..e7e53b4 --- /dev/null +++ b/pasta-tree/src/circuit_fat/mod.rs @@ -0,0 +1,72 @@ +use ark_ec::CurveGroup; +use ark_ff::PrimeField; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{vec, vec::Vec}; +use w3f_pcs::pcs::commitment::WrappedAffine; +use w3f_plonk_common::{ColumnsCommited, ColumnsEvaluated}; + +pub mod params; +pub mod prover; +pub mod verifier; + +#[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct ProofComms { + pub(crate) node_idx: WrappedAffine, + pub(crate) bf_bits: WrappedAffine, + pub(crate) selected_node_acc: WrappedAffine, + pub(crate) blinded_node_acc: [WrappedAffine; 2], + pub(crate) node_idx_sum_acc: WrappedAffine, +} + +impl ColumnsCommited> for ProofComms { + fn to_vec(self) -> Vec> { + self.into() + } +} + +impl From> for Vec> { + fn from(value: ProofComms) -> Self { + let [blinded_node_acc_x, blinded_node_acc_y] = value.blinded_node_acc; + vec![ + value.node_idx, + value.bf_bits, + value.selected_node_acc, + blinded_node_acc_x, + blinded_node_acc_y, + value.node_idx_sum_acc, + ] + } +} + +#[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct ProofEvals { + pub(crate) x_coords: F, + pub(crate) h_powers: [F; 2], + pub(crate) node_idx: F, + pub(crate) bf_bits: F, + pub(crate) selected_node_acc: F, + pub(crate) blinded_node_acc: [F; 2], + pub(crate) node_idx_sum_acc: F, +} + +impl From> for Vec { + fn from(value: ProofEvals) -> Self { + vec![ + value.x_coords, + value.h_powers[0], + value.h_powers[1], + value.node_idx, + value.bf_bits, + value.selected_node_acc, + value.blinded_node_acc[0], + value.blinded_node_acc[1], + value.node_idx_sum_acc, + ] + } +} + +impl ColumnsEvaluated for ProofEvals { + fn to_vec(self) -> Vec { + self.into() + } +} diff --git a/pasta-tree/src/circuit_fat/params.rs b/pasta-tree/src/circuit_fat/params.rs new file mode 100644 index 0000000..59d41ab --- /dev/null +++ b/pasta-tree/src/circuit_fat/params.rs @@ -0,0 +1,157 @@ +use crate::auth_path::node::LevelWitnessWithBlinding; +use crate::circuit_fat::prover::PiopProver; +use crate::circuit_fat::verifier::PiopVerifier; +use crate::{AffinePoint, CircuitParams, CurveModel}; +// use ark_ec::short_weierstrass::{Affine as SwAffine, SWCurveConfig}; +use ark_ec::{AffineRepr, CurveGroup}; +use ark_ff::One; +use ark_ff::{AdditiveGroup, BigInteger, PrimeField, Zero}; +use ark_std::{vec, vec::Vec}; +use w3f_pcs::pcs::commitment::WrappedAffine; +use w3f_plonk_common::FieldColumn; +use w3f_plonk_common::cond_select::CondSelect; +use w3f_plonk_common::domain::Domain; +use w3f_plonk_common::gadgets::booleanity::BitColumn; +use w3f_plonk_common::gadgets::ec::AffineColumn; + +// Hiding Pedersen commitment opened in `2` points. +pub const ZK_ROWS: usize = 2; + +/// Plonk Interactive Oracle Proofs (PIOP) parameters. +#[derive(Clone)] +pub struct PiopParams> { + /// Domain over which the piop is represented. + pub domain: Domain, + /// Number of bits used to represent a scalar. + pub scalar_bitlen: usize, + /// Blinding base point. + pub h: G, +} + +impl> CircuitParams + for PiopParams> +where + G::BaseField: CondSelect, +{ + type Commitments = crate::circuit_fat::ProofComms; + type Evaluations = crate::circuit_fat::ProofEvals; + type ProverCircuit = PiopProver>; + type VerifierCircuit = PiopVerifier>; + + fn prover_circuit( + &self, + level: LevelWitnessWithBlinding>, + ) -> Self::ProverCircuit { + PiopProver::build(&self, level) + } + + fn verifier_circuit( + &self, + instance: (AffinePoint, C::Affine), + fixed_cols: &[WrappedAffine], + cols: Self::Commitments, + evals: Self::Evaluations, + zeta: C::ScalarField, + ) -> Self::VerifierCircuit { + let h_powers_comm: &[_; 2] = fixed_cols.try_into().expect("Expected 2 fixed columns"); + let domain_at_zeta = self.domain.evaluate(zeta); + let (child, x_parent) = instance; + PiopVerifier::init( + child, + WrappedAffine(x_parent), + domain_at_zeta, + h_powers_comm.clone(), + cols, + evals, + ) + } + + fn fixed_columns(&self) -> Vec> { + let h_powers_col = self.h_powers_column(); + vec![h_powers_col.xs, h_powers_col.ys] + } + + fn tree_nodes_column( + &self, + children_x_coords: &[C::ScalarField], + ) -> FieldColumn { + self.x_coords_column(children_x_coords) + } + + fn max_children(&self) -> usize { + self.max_nodes() + } + + #[cfg(test)] + fn setup(domain_size: usize, h: AffinePoint, _seed: AffinePoint) -> Self { + let domain = Domain::::with_zk_rows(domain_size, ZK_ROWS); + Self::setup(domain, h) + } +} + +impl> PiopParams +where + G::BaseField: CondSelect, +{ + pub fn setup(domain: Domain, h: G) -> Self { + let scalar_bitlen = G::ScalarField::MODULUS_BIT_SIZE as usize; + Self { + domain, + scalar_bitlen, + h, + } + } + + pub fn max_nodes(&self) -> usize { + self.domain.capacity - 1 + } + + pub fn x_coords_column(&self, x_coords: &[G::BaseField]) -> FieldColumn { + let c = self.max_nodes(); + assert!(x_coords.len() <= c); + let mut x_coords = x_coords.to_vec(); + x_coords.resize(self.domain.domain_size(), G::BaseField::zero()); + x_coords[c] = G::BaseField::one(); + self.domain.domains.column_from_evals(x_coords, c) + } + + pub fn h_powers_column(&self) -> AffineColumn { + let mut h_powers = self.powers_of_h(); + h_powers.truncate(self.max_nodes()); + AffineColumn::public_column(h_powers, &self.domain) + } + + pub fn node_selector(&self, node_index: usize) -> BitColumn { + let c = self.max_nodes(); + let mut node_selector = vec![false; c]; + assert!(node_index < c); // allows to select a padding node + node_selector[node_index] = true; + BitColumn::init(node_selector, &self.domain) + } + + pub fn bf_bits_column(&self, bf: G::ScalarField) -> BitColumn { + let mut bf_bits = self.scalar_part(bf); + bf_bits.truncate(self.max_nodes()); + BitColumn::init(bf_bits, &self.domain) + } + + fn powers_of_h(&self) -> Vec { + let mut h = self.h.into_group(); + let mut multiples = Vec::with_capacity(self.scalar_bitlen); + multiples.push(h); + for _ in 1..self.scalar_bitlen { + h.double_in_place(); + multiples.push(h); + } + CurveGroup::normalize_batch(&multiples) + } + + fn scalar_part(&self, e: G::ScalarField) -> Vec { + let bits_with_trailing_zeroes = e.into_bigint().to_bits_le(); + let significant_bits = &bits_with_trailing_zeroes[..self.scalar_bitlen]; + significant_bits.to_vec() + } +} + +#[cfg(test)] +mod tests {} diff --git a/pasta-tree/src/circuit_fat/prover.rs b/pasta-tree/src/circuit_fat/prover.rs new file mode 100644 index 0000000..591172b --- /dev/null +++ b/pasta-tree/src/circuit_fat/prover.rs @@ -0,0 +1,286 @@ +use crate::auth_path::node::LevelWitnessWithBlinding; +use crate::circuit_fat::params::PiopParams; +use crate::circuit_fat::{ProofComms, ProofEvals}; +use crate::{AffinePoint, CurveModel}; +use ark_ec::AffineRepr; +use ark_ec::CurveGroup; +use ark_ff::One; +use ark_ff::{FftField, PrimeField, Zero}; +use ark_poly::Evaluations; +use ark_poly::univariate::DensePolynomial; +use ark_std::{vec, vec::Vec}; +use w3f_pcs::pcs::commitment::WrappedAffine; +use w3f_plonk_common::FieldColumn; +use w3f_plonk_common::cond_select::CondSelect; +use w3f_plonk_common::domain::Domain; +use w3f_plonk_common::gadgets::ProverGadget; +use w3f_plonk_common::gadgets::booleanity::{BitColumn, Booleanity}; +use w3f_plonk_common::gadgets::column_sum::ColumnSumPolys; +use w3f_plonk_common::gadgets::ec::AffineColumn; +use w3f_plonk_common::gadgets::ec::CondAdd; +use w3f_plonk_common::gadgets::equal_cells::CellsEqPolys; +use w3f_plonk_common::gadgets::fixed_cells::FixedCells; +use w3f_plonk_common::gadgets::inner_prod_inv::InnerProdInv; +use w3f_plonk_common::piop::ProverPiop; + +pub struct PiopProver> { + domain: Domain, + // `x` coordinates of all the children of a node. Public input. + x_coords: FieldColumn, + // `H, 2H, 4H,...,2^sH` Fixed column. + h_powers: AffineColumn, + // `node_x = self.x_coords[self.node_idx]` Private input. + node_idx: BitColumn, + // Bits of the chosen blinding factor. Private input. + bf_bits: BitColumn, + + selected_node_acc: FieldColumn, + blinded_node_acc: AffineColumn, + node_idx_sum_acc: FieldColumn, + + gadgets: Vec>>, + result: G, +} + +impl> PiopProver> +where + G::BaseField: CondSelect, +{ + pub fn build( + params: &PiopParams>, + level: LevelWitnessWithBlinding>, + ) -> Self { + let domain = params.domain.clone(); + let x_coords = params.x_coords_column(&level.level_witness.x_coords()); + let h_powers = params.h_powers_column(); + let node_idx = params.node_selector(level.level_witness.path_node_idx); + let bf_bits = params.bf_bits_column(level.bf); + let selected_node = InnerProdInv::init(x_coords.clone(), node_idx.col.clone(), &domain); + + let node = level.level_witness.path_node(); + debug_assert_eq!(selected_node.acc.evals[0], node.x().unwrap()); + // here we witness yi + let blinded_node = CondAdd::init(bf_bits.clone(), h_powers.clone(), node, &domain); + debug_assert_eq!( + blinded_node.seed_plus_sum(), + (node + params.h * level.bf).into_affine() + ); + debug_assert_eq!(blinded_node.acc.xs.evals[0], node.x().unwrap()); + debug_assert_eq!(blinded_node.acc.ys.evals[0], node.y().unwrap()); + let node_idx_bool = Booleanity::init(node_idx.clone()); + let bf_bits_bool = Booleanity::init(bf_bits.clone()); + let node_idx_sum = ColumnSumPolys::init(node_idx.col.clone(), &domain); + let node_idx_sum_vals = FixedCells::init( + node_idx_sum.acc.clone(), + &domain, + G::BaseField::zero(), + G::BaseField::one(), + ); + let seed_eq_node = CellsEqPolys::first_cells( + selected_node.acc.clone(), + blinded_node.acc.xs.clone(), + &domain, + ); + + let result = blinded_node.seed_plus_sum(); + let (node_blinded_x, node_blinded_y) = result.xy().unwrap(); + + let blinded_node_val_x = + FixedCells::last(blinded_node.acc.xs.clone(), &domain, node_blinded_x); + let blinded_node_val_y = + FixedCells::last(blinded_node.acc.ys.clone(), &domain, node_blinded_y); + let selected_node_val = + FixedCells::last(selected_node.acc.clone(), &domain, G::BaseField::zero()); + // this prevents opening to -parent=(x,-y) + // parent = commit([x1, ..., xl, 1, 0, 0, 0]; 0) = x1.G1 + ... + xl.Gl + 1.G_{l+1} + // then -parent = commit([-x1, ..., -xl, -1, 0, 0, 0]; 0) + // TODO: + let mut x_coords_with_one_cell = x_coords.clone(); + x_coords_with_one_cell.payload_len = domain.capacity; + let one_cell = FixedCells::last(x_coords_with_one_cell, &domain, G::BaseField::one()); + + let selected_node_acc = selected_node.acc.clone(); + let blinded_node_acc = blinded_node.acc.clone(); + let node_idx_sum_acc = node_idx_sum.acc.clone(); + + let mut gadgets: Vec>> = Vec::new(); + gadgets.push(Box::new(selected_node)); + gadgets.push(Box::new(blinded_node)); + gadgets.push(Box::new(node_idx_sum)); + gadgets.push(Box::new(node_idx_bool)); + gadgets.push(Box::new(bf_bits_bool)); + gadgets.push(Box::new(node_idx_sum_vals)); + gadgets.push(Box::new(blinded_node_val_x)); + gadgets.push(Box::new(blinded_node_val_y)); + gadgets.push(Box::new(selected_node_val)); + gadgets.push(Box::new(seed_eq_node)); + gadgets.push(Box::new(blinded_node_acc.clone())); + gadgets.push(Box::new(one_cell)); + + Self { + domain, + x_coords, + h_powers, + node_idx, + bf_bits, + selected_node_acc, + blinded_node_acc, + node_idx_sum_acc, + gadgets, + result, + } + } + + fn _committed_columns< + C: CurveGroup, + Fun: Fn(&DensePolynomial) -> WrappedAffine, + >( + &self, + commit: Fun, + ) -> ProofComms { + let node_idx = commit(self.node_idx.as_poly()); + let bf_bits = commit(self.bf_bits.as_poly()); + let selected_node_acc = commit(self.selected_node_acc.as_poly()); + let blinded_node_acc = [ + commit(self.blinded_node_acc.xs.as_poly()), + commit(self.blinded_node_acc.ys.as_poly()), + ]; + let node_idx_sum_acc = commit(self.node_idx_sum_acc.as_poly()); + ProofComms { + node_idx, + bf_bits, + selected_node_acc, + blinded_node_acc, + node_idx_sum_acc, + } + } + + // Should return polynomials in the consistent with + // Self::Evaluations::to_vec() and Self::Commitments::to_vec(). + fn _columns(&self) -> Vec> { + vec![ + self.x_coords.as_poly().clone(), + self.h_powers.xs.as_poly().clone(), + self.h_powers.ys.as_poly().clone(), + self.node_idx.as_poly().clone(), + self.bf_bits.as_poly().clone(), + self.selected_node_acc.as_poly().clone(), + self.blinded_node_acc.xs.as_poly().clone(), + self.blinded_node_acc.ys.as_poly().clone(), + self.node_idx_sum_acc.as_poly().clone(), + ] + } + + fn _columns_evaluated(&self, zeta: &G::BaseField) -> ProofEvals { + let x_coords = self.x_coords.evaluate(zeta); + let h_powers = [ + self.h_powers.xs.evaluate(zeta), + self.h_powers.ys.evaluate(zeta), + ]; + let node_idx = self.node_idx.evaluate(zeta); + let bf_bits = self.bf_bits.evaluate(zeta); + let blinded_node_acc = [ + self.blinded_node_acc.xs.evaluate(zeta), + self.blinded_node_acc.ys.evaluate(zeta), + ]; + let selected_node_acc = self.selected_node_acc.evaluate(zeta); + let node_idx_sum_acc = self.node_idx_sum_acc.evaluate(zeta); + ProofEvals { + x_coords, + h_powers, + node_idx, + bf_bits, + selected_node_acc, + blinded_node_acc, + node_idx_sum_acc, + } + } +} + +impl> + ProverPiop> for PiopProver> +where + G::BaseField: CondSelect, +{ + const N_COLUMNS: usize = 9; + const N_CONSTRAINTS: usize = 13; + const N_QUOTIENT_CHUNKS: usize = 3; + + type Commitments = ProofComms; + type Evaluations = ProofEvals; + type Instance = AffinePoint; + + fn committed_columns) -> WrappedAffine>( + &self, + commit: Fun, + ) -> Self::Commitments { + self._committed_columns(commit) + } + + // Should return polynomials in the consistent with + // Self::Evaluations::to_vec() and Self::Commitments::to_vec(). + fn columns(&self) -> Vec> { + self._columns() + } + + fn columns_evaluated(&self, zeta: &C::ScalarField) -> Self::Evaluations { + self._columns_evaluated(zeta) + } + + fn constraints(&self) -> Vec> { + self.gadgets.iter().flat_map(|g| g.constraints()).collect() + } + + fn quotient(&self, alphas: &[C::ScalarField]) -> Option>> { + >>::_quotient_chunks(self, alphas) + } + + fn constraints_lin(&self, zeta: &C::ScalarField) -> Vec> { + self.gadgets + .iter() + .flat_map(|g| g.constraints_linearized(zeta)) + .collect() + } + + fn domain(&self) -> &Domain { + &self.domain + } + + fn result(&self) -> Self::Instance { + self.result + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::random_witness; + use ark_bls12_381::G1Projective; + use ark_ed_on_bls12_381_bandersnatch::{Fq, Fr, SWAffine}; + use ark_std::{UniformRand, test_rng}; + use w3f_pcs::pcs::commitment::WrappedAffine; + + #[test] + fn test_constraints() { + let rng = &mut test_rng(); + + let domain_size = 256; + let domain = Domain::::with_zk_rows(domain_size, 3); + + let node = SWAffine::rand(rng); + let h = SWAffine::rand(rng); + let bf = Fr::from(u128::rand(rng)); + let blinded_node = (node + h * bf).into_affine(); + + let piop_params = PiopParams::setup(domain, h); + let witness = + random_witness(piop_params.max_nodes(), node, rng).with_blinding(bf, Fq::zero()); + let piop = PiopProver::build(&piop_params, witness); + + assert!(ProverPiop::<_, WrappedAffine>::constraints_satisfied(&piop)); + assert_eq!( + ProverPiop::<_, WrappedAffine>::result(&piop), + blinded_node + ); + } +} diff --git a/pasta-tree/src/circuit_fat/verifier.rs b/pasta-tree/src/circuit_fat/verifier.rs new file mode 100644 index 0000000..d473ddf --- /dev/null +++ b/pasta-tree/src/circuit_fat/verifier.rs @@ -0,0 +1,189 @@ +use crate::circuit_fat::{ProofComms, ProofEvals}; +// use ark_ec::short_weierstrass::{Affine as SwAffine, SWCurveConfig}; +use crate::{AffinePoint, CurveModel}; +use ark_ec::{AffineRepr, CurveGroup}; +use ark_ff::One; +use ark_ff::Zero; +use ark_std::marker::PhantomData; +use ark_std::{vec, vec::Vec}; +use w3f_pcs::pcs::commitment::WrappedAffine; +use w3f_plonk_common::domain::EvaluatedDomain; +use w3f_plonk_common::gadgets::VerifierGadget; +use w3f_plonk_common::gadgets::booleanity::BooleanityValues; +use w3f_plonk_common::gadgets::column_sum::ColumnSumEvals; +use w3f_plonk_common::gadgets::ec::CondAddValues; +use w3f_plonk_common::gadgets::equal_cells::EqualCells; +use w3f_plonk_common::gadgets::fixed_cells::FixedCellsValues; +use w3f_plonk_common::gadgets::inner_prod_inv::InnerProdInvValues; +use w3f_plonk_common::piop::VerifierPiop; + +pub struct PiopVerifier> { + domain_evals: EvaluatedDomain, + instance: G, + x_coords_comm: WrappedAffine, + h_powers_comm: [WrappedAffine; 2], + witness_columns: ProofComms, + // Gadget verifiers: + selected_node: InnerProdInvValues, + blinded_node: CondAddValues, + node_idx_sum: ColumnSumEvals, + node_idx_bool: BooleanityValues, + bf_bits_bool: BooleanityValues, + node_idx_sum_vals: FixedCellsValues, + seed_eq_node: EqualCells, +} + +impl> PiopVerifier { + pub fn init( + instance: G, + blinded_parent: WrappedAffine, + domain_evals: EvaluatedDomain, + h_powers_comm: [WrappedAffine; 2], + witness_columns: ProofComms, + all_evals: ProofEvals, + ) -> Self { + let selected_node = InnerProdInvValues { + a: all_evals.x_coords, + b: all_evals.node_idx, + not_last: domain_evals.not_last_row, + acc: all_evals.selected_node_acc, + }; + let blinded_node = CondAddValues { + bitmask: all_evals.bf_bits, + points: (all_evals.h_powers[0], all_evals.h_powers[1]), + not_last: domain_evals.not_last_row, + acc: (all_evals.blinded_node_acc[0], all_evals.blinded_node_acc[1]), + _phantom: PhantomData, + }; + let node_idx_sum = ColumnSumEvals { + col: all_evals.node_idx, + acc: all_evals.node_idx_sum_acc, + not_last: domain_evals.not_last_row, + }; + let node_idx_bool = BooleanityValues { + bits: all_evals.node_idx, + }; + let bf_bits_bool = BooleanityValues { + bits: all_evals.bf_bits, + }; + let node_idx_sum_vals = FixedCellsValues { + col: all_evals.node_idx_sum_acc, + l_i: vec![domain_evals.l_first, domain_evals.l_last], + col_i: vec![C::ScalarField::zero(), C::ScalarField::one()], + }; + let seed_eq_node = EqualCells { + a: selected_node.acc, + b: blinded_node.acc.0, + li: domain_evals.l_first, + }; + Self { + instance, + domain_evals, + x_coords_comm: blinded_parent, + h_powers_comm, + witness_columns, + // gadgets + selected_node, + blinded_node, + node_idx_sum, + node_idx_bool, + bf_bits_bool, + node_idx_sum_vals, + seed_eq_node, + } + } +} + +impl> + VerifierPiop> for PiopVerifier> +{ + const N_COLUMNS: usize = 9; + const N_CONSTRAINTS: usize = 13; + type Instance = AffinePoint; + + fn precommitted_columns(&self) -> Vec> { + vec![ + self.x_coords_comm.clone(), + self.h_powers_comm[0].clone(), + self.h_powers_comm[1].clone(), + ] + } + + fn evaluate_constraints_main(&self) -> Vec { + let (x, y) = self.instance.xy().unwrap(); + // doesn't have to be on curve + let blinded_node_acc = + AffinePoint::::new_unchecked(self.blinded_node.acc.0, self.blinded_node.acc.1); + vec![ + self.selected_node.evaluate_constraints_main(), + self.blinded_node.evaluate_constraints_main(), + self.node_idx_sum.evaluate_constraints_main(), + self.node_idx_bool.evaluate_constraints_main(), + self.bf_bits_bool.evaluate_constraints_main(), + self.node_idx_sum_vals.evaluate_constraints_main(), + vec![FixedCellsValues::evaluate_for_cell( + self.blinded_node.acc.0, + self.domain_evals.l_last, + x, + )], + vec![FixedCellsValues::evaluate_for_cell( + self.blinded_node.acc.1, + self.domain_evals.l_last, + y, + )], + vec![FixedCellsValues::evaluate_for_cell( + self.selected_node.acc, + self.domain_evals.l_last, + C::ScalarField::zero(), + )], + self.seed_eq_node.evaluate_constraints_main(), + blinded_node_acc.evaluate_constraints_main(), + vec![FixedCellsValues::evaluate_for_cell( + self.selected_node.a, + self.domain_evals.l_last, + C::ScalarField::one(), + )], + ] + .concat() + } + + fn lin_poly_commitment( + &self, + agg_coeffs: &[C::ScalarField], + ) -> (Vec, Vec>) { + assert_eq!(agg_coeffs.len(), Self::N_CONSTRAINTS); + + let selected_node_acc = self.witness_columns.selected_node_acc.clone(); + let selected_node_coeff = -agg_coeffs[0] * self.selected_node.not_last; + + let blinded_node_acc_x = self.witness_columns.blinded_node_acc[0].clone(); + let blinded_node_acc_y = self.witness_columns.blinded_node_acc[1].clone(); + let (c_acc_x, c_acc_y) = self.blinded_node.acc_coeffs_1(); + let mut blinded_node_x_coeff = agg_coeffs[1] * c_acc_x; + let mut blinded_node_y_coeff = agg_coeffs[1] * c_acc_y; + let (c_acc_x, c_acc_y) = self.blinded_node.acc_coeffs_2(); + blinded_node_x_coeff += agg_coeffs[2] * c_acc_x; + blinded_node_y_coeff += agg_coeffs[2] * c_acc_y; + + let node_idx_sum_acc = self.witness_columns.node_idx_sum_acc.clone(); + let node_idx_sum_coeff = agg_coeffs[3] * self.node_idx_sum.not_last; + ( + vec![ + selected_node_coeff, + blinded_node_x_coeff, + blinded_node_y_coeff, + node_idx_sum_coeff, + ], + vec![ + selected_node_acc, + blinded_node_acc_x, + blinded_node_acc_y, + node_idx_sum_acc, + ], + ) + } + + fn domain_evaluated(&self) -> &EvaluatedDomain { + &self.domain_evals + } +} diff --git a/pasta-tree/src/circuit_tall/mod.rs b/pasta-tree/src/circuit_tall/mod.rs new file mode 100644 index 0000000..f4f1b6f --- /dev/null +++ b/pasta-tree/src/circuit_tall/mod.rs @@ -0,0 +1,77 @@ +use ark_ec::{CurveGroup, PrimeGroup}; +use ark_ff::PrimeField; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::{vec, vec::Vec}; +use w3f_pcs::pcs::commitment::WrappedAffine; +use w3f_plonk_common::{ColumnsCommited, ColumnsEvaluated}; + +pub mod params; +pub mod prover; +pub mod verifier; + +pub type PiopProof = w3f_plonk_common::PiopProof< + ::ScalarField, + WrappedAffine, + ProofComms, + ProofEvals<::ScalarField>, +>; + +#[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct ProofComms { + /// Witnessed Y-coordinates of the point vector `nodes || h_powers` TODO: last 4 elements + pub(crate) points_y: WrappedAffine, // aka y_parent + /// 0/1 vector `node_idx || bl` + pub(crate) bits: WrappedAffine, + /// Inner product gadget accumulator + pub(crate) inn_prod_acc: WrappedAffine, + /// EC addition (= fixed point multiplication) gadget accumulator + pub(crate) cond_add_acc: [WrappedAffine; 2], +} + +impl ColumnsCommited> for ProofComms { + fn to_vec(self) -> Vec> { + self.into() + } +} + +impl From> for Vec> { + fn from(value: ProofComms) -> Self { + let [cond_add_acc_x, cond_add_acc_y] = value.cond_add_acc; + vec![ + value.points_y, + value.bits, + value.inn_prod_acc, + cond_add_acc_x, + cond_add_acc_y, + ] + } +} + +#[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize)] +pub struct ProofEvals { + pub(crate) points: [F; 2], + pub(crate) ring_selector: F, + pub(crate) bits: F, + pub(crate) inn_prod_acc: F, + pub(crate) cond_add_acc: [F; 2], +} + +impl ColumnsEvaluated for ProofEvals { + fn to_vec(self) -> Vec { + self.into() + } +} + +impl From> for Vec { + fn from(value: ProofEvals) -> Self { + vec![ + value.points[0], + value.ring_selector, + value.points[1], + value.bits, + value.inn_prod_acc, + value.cond_add_acc[0], + value.cond_add_acc[1], + ] + } +} diff --git a/pasta-tree/src/circuit_tall/params.rs b/pasta-tree/src/circuit_tall/params.rs new file mode 100644 index 0000000..7b888e3 --- /dev/null +++ b/pasta-tree/src/circuit_tall/params.rs @@ -0,0 +1,198 @@ +use crate::auth_path::node::LevelWitnessWithBlinding; +use crate::circuit_tall::prover::PiopProver; +use crate::circuit_tall::verifier::PiopVerifier; +use crate::{AffinePoint, CircuitParams, CurveModel}; +// use ark_ec::short_weierstrass::{Affine as SwAffine, SWCurveConfig}; +use ark_ec::{AffineRepr, CurveGroup}; +use ark_ff::{AdditiveGroup, BigInteger, PrimeField, Zero}; +use ark_ff::{FftField, One}; +use ark_std::{vec, vec::Vec}; +use w3f_pcs::pcs::commitment::WrappedAffine; +use w3f_plonk_common::FieldColumn; +use w3f_plonk_common::cond_select::CondSelect; +use w3f_plonk_common::domain::Domain; +use w3f_plonk_common::gadgets::booleanity::BitColumn; +use w3f_plonk_common::gadgets::ec::AffineColumn; + +// Hiding Pedersen commitment opened in `2` points. +pub const ZK_ROWS: usize = 2; + +// `max_nodes + blinding_bits = domain.capacity - 1` +// where `1` acounts for the `seed` point. +/// Circuit parameters +#[derive(Clone)] +pub struct PiopParams> { + /// Domain over which the circuit is represented. + pub domain: Domain, + /// Maximal number of children per tree node. + pub max_nodes: usize, + /// Number of bits used to represent a blinding factor. + pub blinding_bits: usize, + /// Point that initializes the EC addition gadget accumulator. + pub seed: G, + /// Pedersen blinding base point. + pub h: G, +} + +impl> CircuitParams + for PiopParams> +where + G::BaseField: CondSelect, +{ + type Commitments = crate::circuit_tall::ProofComms; + type Evaluations = crate::circuit_tall::ProofEvals; + type ProverCircuit = PiopProver>; + type VerifierCircuit = PiopVerifier>; + + fn prover_circuit( + &self, + level: LevelWitnessWithBlinding>, + ) -> Self::ProverCircuit { + PiopProver::build(&self, level) + } + + fn verifier_circuit( + &self, + instance: (AffinePoint, C::Affine), + fixed_cols: &[WrappedAffine], + cols: Self::Commitments, + evals: Self::Evaluations, + zeta: C::ScalarField, + ) -> Self::VerifierCircuit { + assert_eq!(fixed_cols.len(), 1, "Expected 1 fixed columns"); + let selector = fixed_cols[0].clone(); + let domain_at_zeta = self.domain.evaluate(zeta); + let (child, x_parent) = instance; + PiopVerifier::init( + domain_at_zeta, + WrappedAffine(x_parent), + selector, + cols, + evals, + self.seed, + child, + ) + } + + fn fixed_columns(&self) -> Vec> { + vec![self.select_part()] + } + + fn tree_nodes_column(&self, children_x_coords: &[G::BaseField]) -> FieldColumn { + assert!(children_x_coords.len() <= self.max_nodes); + let mut x_coords = children_x_coords.to_vec(); + // padding + x_coords.resize(self.max_nodes, G::BaseField::zero()); + // `powers_of_h` x-coords + let powers_of_h = self.power_of_h(); + assert_eq!(powers_of_h.len(), self.blinding_bits); + let powers_of_h_xs = powers_of_h.into_iter().filter_map(|p| p.x()); + x_coords.extend(powers_of_h_xs); + let payload_len = self.domain.capacity - 1; + assert_eq!(x_coords.len(), payload_len); + // x_coords.push(G::BaseField::one()); + // assert_eq!(x_coords.len(), self.domain.capacity); + + // zk_rows + x_coords.resize(self.domain.domain_size(), G::BaseField::zero()); + self.domain.domains.column_from_evals(x_coords, payload_len) + } + + fn max_children(&self) -> usize { + self.max_nodes + } + + #[cfg(test)] + fn setup(domain_size: usize, h: AffinePoint, seed: AffinePoint) -> Self { + let domain = Domain::::with_zk_rows(domain_size, ZK_ROWS); + Self::setup(domain, h, seed) + } +} + +impl> PiopParams +where + G::BaseField: CondSelect, +{ + pub fn setup(domain: Domain, h: G, seed: G) -> Self { + assert!(domain.domain_size() > 256); + let actual_capacity = domain.capacity - 1; + let domain_fat = Domain::::with_zk_rows(256, domain.zk_rows); + let scalar_size = domain_fat.capacity - 1; + let blinding_bits = + ark_std::cmp::min(G::ScalarField::MODULUS_BIT_SIZE as usize, scalar_size); + let max_nodes = actual_capacity - blinding_bits; + Self { + domain, + max_nodes, + blinding_bits, + seed, + h, + } + } + + // fn x_coords_from_points(&self, child_nodes: Vec) -> FieldColumn { + // let points = self.siblings_with_blinding(child_nodes); + // let (mut x_coords, mut y_coords): (Vec, Vec) = + // points.iter().map(|p| p.xy().unwrap()).unzip(); + // let payload_len = self.domain.capacity - 1; + // assert_eq!(x_coords.len(), payload_len); + // // x_coords.push(G::BaseField::one()); + // // assert_eq!(x_coords.len(), self.domain.capacity); + // + // // zk_rows + // x_coords.resize(self.domain.domain_size(), G::BaseField::zero()); + // y_coords.resize(self.domain.domain_size(), G::BaseField::zero()); + // self.domain.domains.column_from_evals(x_coords, payload_len) + // } + + pub fn points_column(&self, child_nodes: Vec) -> AffineColumn { + let points = self.siblings_with_blinding(child_nodes); + assert_eq!(points.len(), self.domain.capacity - 1); + AffineColumn::public_column(points, &self.domain) + } + + fn siblings_with_blinding(&self, siblings: Vec) -> Vec { + assert!(siblings.len() <= self.max_nodes); + let mut points = siblings; + points.resize(self.max_nodes, G::ZERO); // padding + points.extend(self.power_of_h()); // powers of `H` + points + } + + pub fn bits_column(&self, node_index: usize, bf: G::ScalarField) -> BitColumn { + let mut bits = vec![false; self.max_nodes]; + assert!(node_index < self.max_nodes); // allows to select a padding node + bits[node_index] = true; + bits.extend(self.scalar_part(bf)); + BitColumn::init(bits, &self.domain) + } + + pub(super) fn select_part(&self) -> FieldColumn { + let selector = [ + vec![G::BaseField::one(); self.max_nodes], + vec![G::BaseField::zero(); self.blinding_bits], + ] + .concat(); + self.domain.public_column(selector) + } + + fn power_of_h(&self) -> Vec { + let mut h = self.h.into_group(); + let mut res = Vec::with_capacity(self.blinding_bits); + res.push(h); + for _ in 1..self.blinding_bits { + h.double_in_place(); + res.push(h); + } + CurveGroup::normalize_batch(&res) + } + + fn scalar_part(&self, e: G::ScalarField) -> Vec { + let bits_with_trailing_zeroes = e.into_bigint().to_bits_le(); + let significant_bits = &bits_with_trailing_zeroes[..self.blinding_bits]; + significant_bits.to_vec() + } +} + +#[cfg(test)] +mod tests {} diff --git a/pasta-tree/src/circuit_tall/prover.rs b/pasta-tree/src/circuit_tall/prover.rs new file mode 100644 index 0000000..24687c9 --- /dev/null +++ b/pasta-tree/src/circuit_tall/prover.rs @@ -0,0 +1,212 @@ +use crate::auth_path::node::LevelWitnessWithBlinding; +use crate::circuit_tall::params::PiopParams; +use crate::circuit_tall::{ProofComms, ProofEvals}; +use crate::{AffinePoint, CurveModel}; +use ark_ec::{AffineRepr, CurveGroup}; +use ark_ff::{FftField, One, Zero}; +use ark_poly::Evaluations; +use ark_poly::Polynomial; +use ark_poly::univariate::DensePolynomial; +use ark_std::{vec, vec::Vec}; +use w3f_pcs::pcs::commitment::WrappedAffine; +use w3f_plonk_common::FieldColumn; +use w3f_plonk_common::cond_select::CondSelect; +use w3f_plonk_common::domain::Domain; +use w3f_plonk_common::gadgets::ProverGadget; +use w3f_plonk_common::gadgets::booleanity::{BitColumn, Booleanity}; +use w3f_plonk_common::gadgets::ec::AffineColumn; +use w3f_plonk_common::gadgets::ec::CondAdd; +use w3f_plonk_common::gadgets::fixed_cells::FixedCells; +use w3f_plonk_common::gadgets::inner_prod::InnerProd; +use w3f_plonk_common::piop::ProverPiop; + +pub struct PiopProver> { + domain: Domain, + // `x` coordinates of all the children of a node. Public input. + // `H, 2H, 4H,...,2^sH` Fixed column. + points: AffineColumn, + // `node_x = self.x_coords[self.node_idx]` Private input. + // Bits of the chosen blinding factor. Private input. + bits: BitColumn, + select_part: FieldColumn, + inner_prod_acc: DensePolynomial, + cond_add_acc_x: DensePolynomial, + cond_add_acc_y: DensePolynomial, + gadgets: Vec>>, + result: G, +} + +impl> PiopProver> +where + G::BaseField: CondSelect, +{ + pub fn build( + params: &PiopParams>, + level: LevelWitnessWithBlinding>, + ) -> Self { + let domain = params.domain.clone(); + let points = params.points_column(level.level_witness.siblings); + let bits = params.bits_column(level.level_witness.path_node_idx, level.bf); + let bits_bool = Booleanity::init(bits.clone()); + let select_part = params.select_part(); + let inner_prod = InnerProd::init(select_part.clone(), bits.col.clone(), &domain); + let inner_prod_vals = FixedCells::init( + inner_prod.acc.clone(), + &domain, + G::BaseField::zero(), + G::BaseField::one(), + ); + let cond_add = CondAdd::init(bits.clone(), points.clone(), params.seed, &domain); + let (seed_x, seed_y) = params.seed.xy().unwrap(); + let (result_x, result_y) = cond_add.seed_plus_sum().xy().unwrap(); + let cond_add_vals_x = FixedCells::init(cond_add.acc.xs.clone(), &domain, seed_x, result_x); + let cond_add_vals_y = FixedCells::init(cond_add.acc.ys.clone(), &domain, seed_y, result_y); + + let inner_prod_acc = inner_prod.acc.as_poly().clone(); + let cond_add_acc_x = cond_add.acc.xs.as_poly().clone(); + let cond_add_acc_y = cond_add.acc.ys.as_poly().clone(); + let result = cond_add.result(); + + let mut gadgets: Vec>> = Vec::new(); + gadgets.push(Box::new(inner_prod)); + gadgets.push(Box::new(cond_add)); + gadgets.push(Box::new(bits_bool)); + gadgets.push(Box::new(cond_add_vals_x)); + gadgets.push(Box::new(cond_add_vals_y)); + gadgets.push(Box::new(inner_prod_vals)); + + Self { + domain, + + points, + bits, + select_part, + + gadgets, + inner_prod_acc, + cond_add_acc_x, + cond_add_acc_y, + result, + } + } +} + +impl> + ProverPiop> for PiopProver> +{ + const N_COLUMNS: usize = 7; + const N_CONSTRAINTS: usize = 7; + const N_QUOTIENT_CHUNKS: usize = 3; + + type Commitments = ProofComms; + type Evaluations = ProofEvals; + type Instance = AffinePoint; + + fn committed_columns) -> WrappedAffine>( + &self, + commit: Fun, + ) -> Self::Commitments { + let points_y = commit(self.points.ys.as_poly()); + let bits = commit(self.bits.as_poly()); + let cond_add_acc = [commit(&self.cond_add_acc_x), commit(&self.cond_add_acc_y)]; + let inn_prod_acc = commit(&self.inner_prod_acc); + ProofComms { + points_y, + bits, + cond_add_acc, + inn_prod_acc, + } + } + + // Should return polynomials in the consistent with + // Self::Evaluations::to_vec() and Self::Commitments::to_vec(). + fn columns(&self) -> Vec> { + vec![ + self.points.xs.as_poly().clone(), + self.select_part.as_poly().clone(), + self.points.ys.as_poly().clone(), + self.bits.as_poly().clone(), + self.inner_prod_acc.clone(), + self.cond_add_acc_x.clone(), + self.cond_add_acc_y.clone(), + ] + } + + fn columns_evaluated(&self, zeta: &C::ScalarField) -> Self::Evaluations { + let points = [self.points.xs.evaluate(zeta), self.points.ys.evaluate(zeta)]; + let ring_selector = self.select_part.evaluate(zeta); + let bits = self.bits.evaluate(zeta); + let inn_prod_acc = self.inner_prod_acc.evaluate(zeta); + let cond_add_acc = [ + self.cond_add_acc_x.evaluate(zeta), + self.cond_add_acc_y.evaluate(zeta), + ]; + ProofEvals { + points, + ring_selector, + bits, + inn_prod_acc, + cond_add_acc, + } + } + + fn constraints(&self) -> Vec> { + self.gadgets.iter().flat_map(|g| g.constraints()).collect() + } + + fn quotient(&self, alphas: &[C::ScalarField]) -> Option>> { + >>::_quotient_chunks(self, alphas) + } + + fn constraints_lin(&self, zeta: &C::ScalarField) -> Vec> { + self.gadgets + .iter() + .flat_map(|g| g.constraints_linearized(zeta)) + .collect() + } + + fn domain(&self) -> &Domain { + &self.domain + } + + fn result(&self) -> Self::Instance { + self.result + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::random_witness; + use ark_bls12_381::G1Projective; + use ark_ec::CurveGroup; + use ark_ff::Zero; + + use ark_ed_on_bls12_381_bandersnatch::{Fq, Fr, SWAffine}; + use ark_std::{UniformRand, test_rng}; + + #[test] + fn test_constraints() { + let rng = &mut test_rng(); + + let domain_size = 512; + let domain = Domain::::with_zk_rows(domain_size, 3); + + let node = SWAffine::rand(rng); + let h = SWAffine::rand(rng); + let seed = SWAffine::rand(rng); + let bf = Fr::from(u128::rand(rng)); + let blinded_node = (node + h * bf).into_affine(); + + let piop_params = PiopParams::setup(domain, h, seed); + let witness = + random_witness(piop_params.max_nodes, node, rng).with_blinding(bf, Fq::zero()); + let piop = PiopProver::build(&piop_params, witness); + + assert!(ProverPiop::<_, WrappedAffine>::constraints_satisfied(&piop)); + assert_eq!( + ProverPiop::<_, WrappedAffine>::result(&piop), + blinded_node + ); + } +} diff --git a/pasta-tree/src/circuit_tall/verifier.rs b/pasta-tree/src/circuit_tall/verifier.rs new file mode 100644 index 0000000..fdac640 --- /dev/null +++ b/pasta-tree/src/circuit_tall/verifier.rs @@ -0,0 +1,147 @@ +use crate::circuit_tall::{ProofComms, ProofEvals}; +use crate::{AffinePoint, CurveModel}; +use ark_ec::AffineRepr; +use ark_ec::CurveGroup; +// use ark_ec::short_weierstrass::{Affine as SwAffine, SWCurveConfig}; +use ark_ff::{One, Zero}; +use ark_std::marker::PhantomData; +use ark_std::{vec, vec::Vec}; +use w3f_pcs::pcs::commitment::WrappedAffine; +use w3f_plonk_common::domain::EvaluatedDomain; +use w3f_plonk_common::gadgets::VerifierGadget; +use w3f_plonk_common::gadgets::booleanity::BooleanityValues; +use w3f_plonk_common::gadgets::ec::CondAddValues; +use w3f_plonk_common::gadgets::fixed_cells::FixedCellsValues; +use w3f_plonk_common::gadgets::inner_prod::InnerProdValues; +use w3f_plonk_common::piop::VerifierPiop; + +pub struct PiopVerifier> { + domain_evals: EvaluatedDomain, + + points_x: WrappedAffine, + select_part: WrappedAffine, + witness_cols: ProofComms, + + // Gadget verifiers: + booleanity: BooleanityValues, + inner_prod: InnerProdValues, + inner_prod_acc: FixedCellsValues, + cond_add: CondAddValues, + cond_add_acc_x: FixedCellsValues, + cond_add_acc_y: FixedCellsValues, +} + +impl> PiopVerifier { + pub fn init( + domain_evals: EvaluatedDomain, + points_x: WrappedAffine, + select_part: WrappedAffine, + witness_cols: ProofComms, + evals: ProofEvals, + seed: G, + result: G, + ) -> Self { + let cond_add = CondAddValues { + bitmask: evals.bits, + points: (evals.points[0], evals.points[1]), + not_last: domain_evals.not_last_row, + acc: (evals.cond_add_acc[0], evals.cond_add_acc[1]), + _phantom: PhantomData, + }; + + let inner_prod = InnerProdValues { + a: evals.ring_selector, + b: evals.bits, + not_last: domain_evals.not_last_row, + acc: evals.inn_prod_acc, + }; + + let booleanity = BooleanityValues { bits: evals.bits }; + + let (seed_x, seed_y) = seed.xy().unwrap(); + let (res_x, res_y) = (seed + result).into_affine().xy().unwrap(); + + let cond_add_acc_x = FixedCellsValues { + col: evals.cond_add_acc[0], + l_i: vec![domain_evals.l_first, domain_evals.l_last], + col_i: vec![seed_x, res_x], + }; + + let cond_add_acc_y = FixedCellsValues { + col: evals.cond_add_acc[1], + l_i: vec![domain_evals.l_first, domain_evals.l_last], + col_i: vec![seed_y, res_y], + }; + + let inner_prod_acc = FixedCellsValues { + col: evals.inn_prod_acc, + l_i: vec![domain_evals.l_first, domain_evals.l_last], + col_i: vec![C::ScalarField::zero(), C::ScalarField::one()], + }; + + Self { + domain_evals, + points_x, + select_part, + witness_cols, + booleanity, + inner_prod, + inner_prod_acc, + cond_add, + cond_add_acc_x, + cond_add_acc_y, + } + } +} + +impl> + VerifierPiop> for PiopVerifier> +{ + const N_COLUMNS: usize = 7; + const N_CONSTRAINTS: usize = 7; + type Instance = AffinePoint; + // type Instance = > as ProverPiop>>::Instance; + + fn precommitted_columns(&self) -> Vec> { + vec![self.points_x.clone(), self.select_part.clone()] + } + + fn evaluate_constraints_main(&self) -> Vec { + vec![ + self.inner_prod.evaluate_constraints_main(), + self.cond_add.evaluate_constraints_main(), + self.booleanity.evaluate_constraints_main(), + self.cond_add_acc_x.evaluate_constraints_main(), + self.cond_add_acc_y.evaluate_constraints_main(), + self.inner_prod_acc.evaluate_constraints_main(), + ] + .concat() + } + + fn lin_poly_commitment( + &self, + alphas: &[C::ScalarField], + ) -> (Vec, Vec>) { + assert_eq!(alphas.len(), Self::N_CONSTRAINTS); + + let inner_prod_acc = self.witness_cols.inn_prod_acc.clone(); + let inner_prod_coeff = alphas[0] * self.inner_prod.not_last; + + let cond_add_acc_x = self.witness_cols.cond_add_acc[0].clone(); + let cond_add_acc_y = self.witness_cols.cond_add_acc[1].clone(); + let (c_acc_x, c_acc_y) = self.cond_add.acc_coeffs_1(); + let mut cond_add_x_coeff = alphas[1] * c_acc_x; + let mut cond_add_y_coeff = alphas[1] * c_acc_y; + let (c_acc_x, c_acc_y) = self.cond_add.acc_coeffs_2(); + cond_add_x_coeff += alphas[2] * c_acc_x; + cond_add_y_coeff += alphas[2] * c_acc_y; + ( + vec![inner_prod_coeff, cond_add_x_coeff, cond_add_y_coeff], + vec![inner_prod_acc.clone(), cond_add_acc_x, cond_add_acc_y], + ) + } + + fn domain_evaluated(&self) -> &EvaluatedDomain { + &self.domain_evals + } +} diff --git a/pasta-tree/src/level/mod.rs b/pasta-tree/src/level/mod.rs index 0f0f8b1..13b82e5 100644 --- a/pasta-tree/src/level/mod.rs +++ b/pasta-tree/src/level/mod.rs @@ -7,7 +7,6 @@ use w3f_pcs::pcs::PCS; use w3f_pcs::pcs::ipa::hiding::HidingIpa; use w3f_pcs::shplonk::AggregateProof; use w3f_plonk_common::PiopProof; -use w3f_ring_proof::piop::{RingCommitments, RingEvaluations}; pub struct LevelProof { piop_proof: PiopProof< diff --git a/pasta-tree/src/level/prover.rs b/pasta-tree/src/level/prover.rs index 651e3fc..995b8fc 100644 --- a/pasta-tree/src/level/prover.rs +++ b/pasta-tree/src/level/prover.rs @@ -13,8 +13,6 @@ use w3f_pcs::pcs::ipa::hiding::HidingIpa; use w3f_pcs::shplonk::Shplonk; use w3f_plonk_common::piop::ProverPiop; use w3f_plonk_common::prover::{PcsOpeningAt2Points, PlonkProver}; -use w3f_ring_proof::ArkTranscript; -use w3f_ring_proof::piop::prover::PiopProver; impl> CycleSideParams> @@ -24,7 +22,7 @@ impl>, rng: &mut R, ) -> (Affine, LevelProof) { - let (fixed_columns, verifier_key) = + let (fixed_columns, verifxier_key) = self.commit_children(&witness.level_witness.siblings, witness.parent_bf); let piop = PiopProver::build( &self.piop_params, @@ -39,7 +37,7 @@ impl, _>::init( self.pcs_params.ck(), - verifier_key, + blinded_node, ArkTranscript::new(b"pasta-tree-level-proof"), ); let (pcs_openings, piop_proof, _transcript) = plonk_prover.reduce_to_pcs_opening(piop); diff --git a/pasta-tree/src/level/verifier.rs b/pasta-tree/src/level/verifier.rs index ec2c5d7..0fb3a45 100644 --- a/pasta-tree/src/level/verifier.rs +++ b/pasta-tree/src/level/verifier.rs @@ -8,8 +8,6 @@ use w3f_pcs::pcs::ipa::hiding::HidingIpa; use w3f_pcs::shplonk::Shplonk; use w3f_plonk_common::piop::VerifierPiop; use w3f_plonk_common::verifier::{PcsOpeningAt2Points, PlonkVerifier}; -use w3f_ring_proof::piop::verifier::PiopVerifier; -use w3f_ring_proof::{ArkTranscript, FixedColumnsCommitted, VerifierKey}; impl> CycleSideParams> diff --git a/pasta-tree/src/lib.rs b/pasta-tree/src/lib.rs index 6cf3a94..8543bb3 100644 --- a/pasta-tree/src/lib.rs +++ b/pasta-tree/src/lib.rs @@ -1,140 +1,177 @@ -use ark_ec::{AffineRepr, CurveGroup, PrimeGroup}; -use ark_ff::{PrimeField, Zero}; -use ark_std::rand::Rng; +use crate::auth_path::node::LevelWitnessWithBlinding; +use ark_ec::short_weierstrass::SWCurveConfig; +use ark_ec::{CurveGroup, PrimeGroup}; +use ark_ff::PrimeField; +use ark_ff::Zero; +use ark_serialize::CanonicalSerialize; +use ark_std::rand::RngCore; use std::marker::PhantomData; use w3f_pcs::aggregation::multiple::ShplonkTranscript; use w3f_pcs::pcs::PCS; -use w3f_pcs::pcs::PcsParams; use w3f_pcs::pcs::commitment::WrappedAffine; use w3f_pcs::pcs::ipa::hiding::HidingIpa; use w3f_pcs::shplonk::AggregateProof; -use w3f_plonk_common::PiopProof; -use w3f_plonk_common::domain::Domain; -use w3f_ring_proof::piop::{FixedColumns, RingCommitments, RingEvaluations}; -use w3f_ring_proof::{FixedColumnsCommitted, PiopParams, VerifierKey}; +use w3f_plonk_common::piop::{ProverPiop, VerifierPiop}; +use w3f_plonk_common::{ColumnsCommited, ColumnsEvaluated, FieldColumn}; pub mod auth_path; -// pub mod circuit; -pub mod level; +pub mod circuit_fat; +pub mod circuit_tall; +// pub mod level; pub mod prover; pub mod verifier; -type IPACommitment = as PCS<::ScalarField>>::C; +pub trait CurveModel: SWCurveConfig {} +impl CurveModel for T where T: SWCurveConfig {} +type AffinePoint = ark_ec::short_weierstrass::Affine; +type ProjectivePoint = ark_ec::short_weierstrass::Projective; + +// TODO: goes vto plonk-common in some form +/// A circuit over `C::ScalarField`. +pub trait CircuitParams> { + type Commitments: ColumnsCommited>; + type Evaluations: ColumnsEvaluated; + type ProverCircuit: ProverPiop< + C::ScalarField, + WrappedAffine, + Instance = AffinePoint, + Commitments = Self::Commitments, + Evaluations = Self::Evaluations, + >; + type VerifierCircuit: VerifierPiop, Instance = AffinePoint>; + + fn prover_circuit( + &self, + level: LevelWitnessWithBlinding>, + ) -> Self::ProverCircuit; + + fn verifier_circuit( + &self, + instance: (AffinePoint, C::Affine), + fixed_cols: &[WrappedAffine], + cols: Self::Commitments, + evals: Self::Evaluations, + zeta: C::ScalarField, + ) -> Self::VerifierCircuit; + + fn fixed_columns(&self) -> Vec>; + + fn tree_nodes_column( + &self, + children_x_coords: &[C::ScalarField], + ) -> FieldColumn; + + fn max_children(&self) -> usize; -pub struct CycleSideParams> { + #[cfg(test)] // an "application" runs usually a single circuit + /// `h` is the pedersen blinding base (from the opposite side) to prove `C' = Ci + rH` + fn setup(domain_size: usize, h: AffinePoint, seed: AffinePoint) -> Self; +} + +pub struct CycleSideParams< + C: CurveGroup, + G: CurveModel, + P: CircuitParams, +> { pcs_params: HidingIpa, - piop_params: PiopParams, + piop_params: P, + phantomm: PhantomData, } pub struct CycleParams< - C0: CurveGroup, - C1: CurveGroup, + C0: CurveModel, + C1: CurveModel, + P0: CircuitParams, C1>, + P1: CircuitParams, C0>, > { - c0_params: CycleSideParams, - c1_params: CycleSideParams, + c0_params: CycleSideParams, C1, P0>, + c1_params: CycleSideParams, C0, P1>, } -#[derive(Clone)] -pub struct CycleSideProof> { - piop_proofs: Vec< - PiopProof, RingCommitments>, RingEvaluations>, - >, - pcs_proof: AggregateProof>, - todo: Coeffs, - fixed_columns_committed: Vec>>, +type LevelProof = w3f_plonk_common::PiopProof< + ::ScalarField, + WrappedAffine, +

>::Commitments, +

>::Evaluations, +>; + +type BatchLevelProof = w3f_plonk_common::PiopProof< + ::ScalarField, + WrappedAffine, + [

>::Commitments; L], + [

>::Evaluations; L], +>; + +#[derive(Clone, Debug)] +pub struct BatchSideProof< + C: CurveGroup, + G: CurveModel, + P: CircuitParams, + const L: usize, +> { + piop_proof: BatchLevelProof, + pcs_proof: AggregateProof>, + todo: Coeffs, } #[derive(Clone)] -pub struct CurveTreeProof< - F0: PrimeField, - F1: PrimeField, - C0: CurveGroup, - C1: CurveGroup, +pub struct CycleSideProof< + C: CurveGroup, + G: CurveModel, + P: CircuitParams, > { - c0_proof: CycleSideProof, - c1_proof: CycleSideProof, + piop_proofs: Vec>, + pcs_proof: AggregateProof>, + todo: Coeffs, } -impl CycleParams -where - F0: PrimeField, - F1: PrimeField, - C0: CurveGroup, - C1: CurveGroup, -{ - pub fn setup(domain_size: usize, rng: &mut R) -> Self { - let setup_degree = 3 * domain_size; - let c0_pcs_params = HidingIpa::::setup(setup_degree, rng); - let c1_pcs_params = HidingIpa::::setup(setup_degree, rng); - let c0_piop_params = piop_params(domain_size, c1_pcs_params.h, rng); - let c1_piop_params = piop_params(domain_size, c0_pcs_params.h, rng); - Self { - c0_params: CycleSideParams { - pcs_params: c0_pcs_params, - piop_params: c0_piop_params, - }, - c1_params: CycleSideParams { - pcs_params: c1_pcs_params, - piop_params: c1_piop_params, - }, - } - } +#[derive(Clone)] +pub struct CurveTreeProof< + C0: CurveModel, + C1: CurveModel, + P0: CircuitParams, C1>, + P1: CircuitParams, C0>, +> { + c0_proof: CycleSideProof, C1, P0>, + c1_proof: CycleSideProof, C0, P1>, } -fn piop_params, R: Rng>( - domain_size: usize, - h: G, - rng: &mut R, -) -> PiopParams { - let domain = Domain::::with_zk_rows(domain_size, 3); - let seed = G::rand(rng); - let padding = G::rand(rng); - PiopParams::setup(domain, h, seed, padding) +#[derive(Clone, Debug)] +pub struct CurveTreeProof2< + C0: CurveModel, + C1: CurveModel, + P0: CircuitParams, C1>, + P1: CircuitParams, C0>, + const L: usize, +> { + c0_proof: BatchSideProof, C1, P0, L>, + c1_proof: BatchSideProof, C0, P1, L>, } -impl> CycleSideParams { - pub fn commit_children( +impl, P: CircuitParams> + CycleSideParams +{ + pub fn commit_tree_nodes( &self, - children: &[G], + nodes_x_coords: &[C::ScalarField], bf: C::ScalarField, - ) -> ( - FixedColumns, - VerifierKey>, - ) { - let fixed_columns = self.piop_params.fixed_columns(&children); - let xs = fixed_columns.points.xs.as_poly(); - let ys = fixed_columns.points.ys.as_poly(); - let fixed_columns_committed = FixedColumnsCommitted { - points: [ - self.pcs_params.commit_hiding(xs, bf).unwrap(), - self.pcs_params - .commit_hiding(ys, C::ScalarField::zero()) - .unwrap(), - ], - ring_selector: self - .pcs_params - .commit_hiding( - fixed_columns.ring_selector.as_poly(), - C::ScalarField::zero(), - ) - .unwrap(), - phantom: PhantomData, - }; - let verifier_key = VerifierKey { - pcs_raw_vk: self.pcs_params.raw_vk(), - fixed_columns_committed, - }; - (fixed_columns, verifier_key) + ) -> Result, ()> { + let nodes_column = +

>::tree_nodes_column(&self.piop_params, nodes_x_coords); + let parent_node = self.pcs_params.commit_hiding(nodes_column.as_poly(), bf); + parent_node } - pub fn commit_nodes( - &self, - nodes: &[G], - // children_x_coords: Vec, - blinding: C::ScalarField, - ) -> Result { - let xs = self.piop_params.points_column(nodes).xs; - Ok(self.pcs_params.commit_hiding(xs.as_poly(), blinding)?.0) + pub fn commit_fixed_columns(&self) -> Vec> { + let fixed_columns =

>::fixed_columns(&self.piop_params); + fixed_columns + .iter() + .map(|c| { + self.pcs_params + .commit_hiding(c.as_poly(), C::ScalarField::zero()) + .unwrap() + }) + .collect() } } @@ -145,6 +182,32 @@ pub enum CycleSide { } #[derive(Clone)] +pub struct ArkTranscript(ark_transcript::Transcript); + +impl> w3f_plonk_common::transcript::PlonkTranscript + for ArkTranscript +{ + fn _128_bit_point(&mut self, label: &'static [u8]) -> F { + self.0.challenge(label).read_reduce() + } + + fn _add_serializable(&mut self, label: &'static [u8], message: &impl CanonicalSerialize) { + self.0.label(label); + self.0.append(message); + } + + fn to_rng(mut self) -> impl RngCore { + self.0.challenge(b"transcript_rng") + } +} + +impl ArkTranscript { + pub fn new(label: &'static [u8]) -> Self { + Self(ark_transcript::Transcript::new_labeled(label)) + } +} + +#[derive(Clone, Debug)] pub struct Coeffs(F, F); impl> ShplonkTranscript for Coeffs { fn get_gamma(&mut self) -> F { @@ -161,37 +224,195 @@ impl> ShplonkTranscript for Coeffs { #[cfg(test)] mod tests { use super::*; + use crate::auth_path::node::LevelWitness; + use crate::auth_path::path::AuthenticationPath; + use crate::circuit_fat::params::PiopParams as CircuitParamsFat; + use crate::circuit_tall::params::PiopParams as CircuitParamsTall; use ark_ec::AdditiveGroup; use ark_ec::scalar_mul::glv::GLVConfig; use ark_ec::scalar_mul::wnaf::WnafContext; - use ark_ec::short_weierstrass::{Affine, Projective, SWCurveConfig}; + use ark_ec::short_weierstrass::{Affine, SWCurveConfig}; use ark_ec::{AffineRepr, CurveGroup}; - use ark_ff::PrimeField; use ark_ff::{BigInteger, Field, Zero}; + use ark_ff::{FftField, PrimeField}; use ark_pallas::PallasConfig; use ark_poly::DenseUVPolynomial; use ark_std::rand::Rng; use ark_std::{UniformRand, cfg_iter_mut, end_timer, start_timer, test_rng}; use ark_vesta::VestaConfig; + use num_format::{Locale, ToFormattedString}; use w3f_pcs::Poly; use w3f_pcs::pcs::PCS; use w3f_pcs::pcs::PcsParams; use w3f_pcs::pcs::ipa::IPA; use w3f_plonk_common::test_helpers::random_vec; - use crate::auth_path::node::LevelWitness; - use crate::auth_path::path::AuthenticationPath; #[cfg(feature = "parallel")] use rayon::prelude::*; type PallasIPA = IPA; - fn random_witness, R: Rng>( - params: &CycleSideParams, + impl CycleParams + where + C0: CurveModel, + C1: CurveModel, + P0: CircuitParams, C1>, + P1: CircuitParams, C0>, + { + pub fn setup(domain_size: usize, rng: &mut R) -> Self { + let setup_degree = 3 * domain_size; + let c0_pcs_params = HidingIpa::>::setup(setup_degree, rng); + let c1_pcs_params = HidingIpa::>::setup(setup_degree, rng); + let c0_piop_params = + P0::setup(domain_size, c1_pcs_params.h, AffinePoint::::rand(rng)); + let c1_piop_params = + P1::setup(domain_size, c0_pcs_params.h, AffinePoint::::rand(rng)); + Self { + c0_params: CycleSideParams { + pcs_params: c0_pcs_params, + piop_params: c0_piop_params, + phantomm: PhantomData, + }, + c1_params: CycleSideParams { + pcs_params: c1_pcs_params, + piop_params: c1_piop_params, + phantomm: PhantomData, + }, + } + } + } + + #[test] + fn test_circuit_tall() { + _test_proof::< + PallasConfig, + VestaConfig, + CircuitParamsTall, + CircuitParamsTall, + >(9, 2); + } + + // cargo test test_circuit_fat --release --features="print-trace" -- --show-output + // cargo test test_circuit_fat --release --features="print-trace parallel" -- --show-output + #[test] + fn test_circuit_fat() { + _test_proof::< + PallasConfig, + VestaConfig, + CircuitParamsFat, + CircuitParamsFat, + >(8, 4); + } + + // cargo test test_bench_curve_tree --release --features="print-trace" -- --show-output --ignored + // cargo test test_bench_curve_tree --release --features="print-trace parallel" -- --show-output --ignored + #[test] + #[ignore] + fn test_bench_curve_tree() { + let (log_n, h) = (8, 2); + println!("n = {}, height = {h}, FAT", 1 << log_n); + _test_proof::< + PallasConfig, + VestaConfig, + CircuitParamsFat, + CircuitParamsFat, + >(log_n, h); + println!(); + + let (log_n, h) = (9, 2); + println!("n = {}, height = {h}, TALL", 1 << log_n); + _test_proof::< + PallasConfig, + VestaConfig, + CircuitParamsTall, + CircuitParamsTall, + >(log_n, h); + println!(); + + let (log_n, h) = (10, 2); + println!("n = {}, height = {h}, TALL", 1 << log_n); + _test_proof::< + PallasConfig, + VestaConfig, + CircuitParamsTall, + CircuitParamsTall, + >(log_n, h); + println!(); + + let (log_n, h) = (8, 4); + println!("n = {}, height = {h}, FAT", 1 << log_n); + _test_proof::< + PallasConfig, + VestaConfig, + CircuitParamsFat, + CircuitParamsFat, + >(log_n, h); + println!(); + + let (log_n, h) = (10, 4); + println!("n = {}, height = {h}, TALL", 1 << log_n); + _test_proof::< + PallasConfig, + VestaConfig, + CircuitParamsTall, + CircuitParamsTall, + >(log_n, h); + println!(); + } + + fn _test_proof(log_n: usize, height: usize) + where + C0: CurveModel, + C1: CurveModel, + P0: CircuitParams, C1>, + P1: CircuitParams, C0>, + { + let rng = &mut test_rng(); + let domain_size = 1 << log_n; + let params = CycleParams::::setup(domain_size, rng); + let (_leaf, path, wrapped_root) = random_path(¶ms, height, rng); + let root = match wrapped_root { + CycleSide::C0(root) => root, //TODO: panics on odd height + _ => panic!(), + }; + let max_nodes = params.c0_params.piop_params.max_children(); + println!( + "capacity=**{}**, arity={max_nodes}", + max_nodes + .pow(height as u32) + .to_formatted_string(&Locale::en) + ); + let t_prove = + start_timer!(|| format!("Proving membership, height={height}, domain={domain_size}")); + let (auth_path, proof) = params.prove(path.clone(), rng); + end_timer!(t_prove); + + let t_verify = start_timer!(|| "Verifying membership"); + let valid = params.verify(auth_path, proof, root); + end_timer!(t_verify); + assert!(valid); + + // number of columns for the FAT scheme is hardcoded in batch.rs + if height == 4 && log_n == 8 { + println!("\n\n"); + let t_prove = start_timer!(|| format!( + "Batch-proving membership, height={height}, domain={domain_size}" + )); + let (auth_path, proof) = params.batch_prove::<_, 2>(path, rng); + end_timer!(t_prove); + + let t_verify = start_timer!(|| "Verifying membership batch-proof"); + let valid = params.batch_verify::<2>(auth_path, proof, root); + end_timer!(t_verify); + assert!(valid); + } + } + + pub fn random_witness, R: Rng>( + capacity: usize, path_node: G, rng: &mut R, ) -> LevelWitness { - let capacity = params.piop_params.keyset_part_size; let mut nodes = random_vec::(capacity, rng); let i = rng.gen_range(0..capacity); nodes[i] = path_node; @@ -201,28 +422,35 @@ mod tests { } } - pub fn random_nodes, R: Rng>( - params: &CycleSideParams, - path_node: G, + pub fn random_nodes< + C: CurveGroup, + G: CurveModel, + P: CircuitParams, + R: Rng, + >( + params: &CycleSideParams, + path_node: AffinePoint, rng: &mut R, - ) -> (C::Affine, LevelWitness) { - let level_witness = random_witness(params, path_node, rng); + ) -> (C::Affine, LevelWitness>) { + let level_witness = random_witness(params.piop_params.max_children(), path_node, rng); let parent = level_witness.compute_parent(params).unwrap(); (parent, level_witness) } pub fn random_path< - C0: CurveGroup, - C1: CurveGroup, + C0: CurveModel, + C1: CurveModel, + P0: CircuitParams, C1>, + P1: CircuitParams, C0>, R: Rng, >( - params: &CycleParams, + params: &CycleParams, length: usize, rng: &mut R, ) -> ( - C0::Affine, - AuthenticationPath, - CycleSide, + AffinePoint, + AuthenticationPath, ProjectivePoint>, + CycleSide, AffinePoint>, ) { let c0_len = (length + 1) / 2; let c1_len = length / 2; @@ -230,7 +458,7 @@ mod tests { let mut c0_path = Vec::with_capacity(c0_len); let mut c1_path = Vec::with_capacity(c1_len); - let leaf = C0::Affine::rand(rng); + let leaf = AffinePoint::::rand(rng); let mut c0_path_node = leaf; for _ in 0..c1_len { let (parent_on_c1, c0_nodes) = random_nodes(¶ms.c1_params, c0_path_node, rng); @@ -252,46 +480,6 @@ mod tests { (leaf, path, root) } - fn _test_proof(log_n: usize, height: usize) - where - F0: PrimeField, - F1: PrimeField, - C0: SWCurveConfig, - C1: SWCurveConfig, - { - let rng = &mut test_rng(); - - let domain_size = 1 << log_n; - let params = CycleParams::, Projective>::setup(domain_size, rng); - let (_leaf, path, wrapped_root) = random_path(¶ms, height, rng); - let _root = match wrapped_root { - CycleSide::C0(root) => root, //TODO: panics on odd height - _ => panic!(), - }; - - let capacity = params.c0_params.piop_params.keyset_part_size; - let t_prove = start_timer!(|| format!( - "Proving CurveTree membership, H={height}, M={}, C={}, C^{height}={}", - domain_size, - capacity, - capacity.pow(height as u32) - )); - let (auth_path, proof) = params.prove(path, rng); - end_timer!(t_prove); - - let t_verify = start_timer!(|| "Verifying CurveTree opening"); - let valid = params.verify(auth_path, proof, wrapped_root); - end_timer!(t_verify); - assert!(valid); - } - - // cargo test test_proof --release --features="print-trace" -- --show-output - // cargo test test_proof --release --features="print-trace parallel" -- --show-output - #[test] - fn test_proof() { - _test_proof::<_, _, PallasConfig, VestaConfig>(9, 4); - } - fn _bench_msm(log_n: u32) { let rng = &mut test_rng(); let n = 2usize.pow(log_n); diff --git a/pasta-tree/src/prover.rs b/pasta-tree/src/prover.rs index 0a070f9..24630f2 100644 --- a/pasta-tree/src/prover.rs +++ b/pasta-tree/src/prover.rs @@ -1,87 +1,120 @@ use crate::auth_path::blinded::BlindedAuthenticationPath; use crate::auth_path::node::LevelWitnessWithBlinding; use crate::auth_path::path::AuthenticationPath; -use crate::{Coeffs, CurveTreeProof, CycleParams, CycleSideParams, CycleSideProof, IPACommitment}; +use crate::{ + AffinePoint, CircuitParams, CurveModel, CycleParams, CycleSideParams, ProjectivePoint, +}; +use crate::{ArkTranscript, BatchSideProof, CurveTreeProof2}; +use crate::{Coeffs, CurveTreeProof, CycleSideProof}; use ark_ec::CurveGroup; -use ark_ec::short_weierstrass::{Affine, Projective, SWCurveConfig}; use ark_ff::{PrimeField, Zero}; -use ark_std::UniformRand; +use ark_poly::Polynomial; use ark_std::rand::Rng; +use ark_std::{UniformRand, end_timer, start_timer}; use std::collections::BTreeSet; +use std::marker::PhantomData; use w3f_pcs::pcs::PcsParams; +use w3f_pcs::pcs::commitment::WrappedAffine; use w3f_pcs::pcs::ipa::hiding::HidingIpa; use w3f_pcs::shplonk::Shplonk; -use w3f_plonk_common::piop::ProverPiop; +use w3f_plonk_common::batch::BatchProver; +use w3f_plonk_common::piop::{ProverPiop, VerifierPiop}; use w3f_plonk_common::prover::{PcsOpeningAt2Points, PlonkProver}; -use w3f_ring_proof::ArkTranscript; -use w3f_ring_proof::piop::prover::PiopProver; -impl CycleParams, Projective> +impl CycleParams where - F0: PrimeField, - F1: PrimeField, - C0: SWCurveConfig, - C1: SWCurveConfig, + C0: CurveModel, + C1: CurveModel, + P0: CircuitParams, C1>, + P1: CircuitParams, C0>, { pub fn prove( &self, - auth_path: AuthenticationPath, Projective>, + auth_path: AuthenticationPath, ProjectivePoint>, rng: &mut R, ) -> ( - BlindedAuthenticationPath, Projective>, - CurveTreeProof, Projective>, + BlindedAuthenticationPath, ProjectivePoint>, + CurveTreeProof, ) { let auth_path_with_bf = auth_path.with_blinding(rng); - let blinded_auth_path = auth_path_with_bf.apply_bfs(&self); + let blinded_auth_path = + auth_path_with_bf.apply_bfs(&self.c0_params.pcs_params, &self.c1_params.pcs_params); let auth_path = blinded_auth_path.clone(); let c0_proof = self.c0_params - .prove_side(blinded_auth_path.c1_path, &auth_path_with_bf.c1_path, rng); + .prove_side(blinded_auth_path.c1_path, auth_path_with_bf.c1_path, rng); let c1_proof = self.c1_params - .prove_side(blinded_auth_path.c0_path, &auth_path_with_bf.c0_path, rng); + .prove_side(blinded_auth_path.c0_path, auth_path_with_bf.c0_path, rng); (auth_path, CurveTreeProof { c0_proof, c1_proof }) } + + pub fn batch_prove( + &self, + auth_path: AuthenticationPath, ProjectivePoint>, + rng: &mut R, + ) -> ( + BlindedAuthenticationPath, ProjectivePoint>, + CurveTreeProof2, + ) { + let auth_path_with_bf = auth_path.with_blinding(rng); + let blinded_auth_path = + auth_path_with_bf.apply_bfs(&self.c0_params.pcs_params, &self.c1_params.pcs_params); + let auth_path = blinded_auth_path.clone(); + let c1_path: [_; L] = auth_path_with_bf.c1_path.try_into().unwrap(); + let c0_path: [_; L] = auth_path_with_bf.c0_path.try_into().unwrap(); + let c0_proof = self + .c0_params + .batch_prove_side(blinded_auth_path.c1_path, c1_path, rng); + let c1_proof = self + .c1_params + .batch_prove_side(blinded_auth_path.c0_path, c0_path, rng); + (auth_path, CurveTreeProof2 { c0_proof, c1_proof }) + } } -impl> - CycleSideParams> +impl, P: CircuitParams> + CycleSideParams { pub fn prove_side( &self, - blinded_path: Vec>, - witness: &[LevelWitnessWithBlinding>], + blinded_path: Vec>, + witness: Vec>>, rng: &mut R, - ) -> CycleSideProof { - debug_assert_eq!(blinded_path.len(), witness.len()); - let mut piop_proofs = Vec::with_capacity(witness.len()); - let mut fixed_columns_committed = Vec::with_capacity(witness.len()); - let mut polys = Vec::with_capacity(witness.len() * 9); - let mut coords = Vec::with_capacity(witness.len() * 9); - let mut bfs = Vec::with_capacity(witness.len() * 9); + ) -> CycleSideProof { + let curve_name = &std::any::type_name::()[53..]; + // println!("\n\nprover {curve_name}\nchildren={blinded_path:?}\n"); + let n_levels = witness.len(); // number of tree levels on this side + debug_assert_eq!(blinded_path.len(), n_levels); + let mut piop_proofs = Vec::with_capacity(n_levels); + + // per tree level + let n_columns = P::VerifierCircuit::N_COLUMNS; + let n_to_commit = n_columns + 3; // plus the quotient chunks + let n_to_open = n_columns + 2; // plus the (folded) quotient (chunks) and the linearization polynomial + + // per side + let n_openings = n_levels * n_to_open; + let mut polys_to_open = Vec::with_capacity(n_openings); + let mut at_coords = Vec::with_capacity(n_openings); + let mut with_bfs = Vec::with_capacity(n_openings); let plonk_prover = PlonkProver::, _>::init( self.pcs_params.ck(), - blinded_path.clone(), + (), ArkTranscript::new(b"pasta-tree-level-proof"), ); - for (level, blinded_node) in witness.iter().zip(blinded_path.into_iter()) { - let (fixed_columns, verifier_key) = - self.commit_children(&level.level_witness.siblings, level.parent_bf); - // debug_assert_eq!(verifier_key.fixed_columns_committed.points[0].0, *blinded_node); - fixed_columns_committed.push(verifier_key.fixed_columns_committed); - let piop = PiopProver::build( - &self.piop_params, - fixed_columns, - level.level_witness.path_node_idx, - level.bf, - ); - let blinded_node_ = > as ProverPiop< - C::ScalarField, - IPACommitment, - >>::result(&piop); - debug_assert_eq!(blinded_node_, blinded_node); + let t_commit_side = start_timer!(|| format!( + "Committing {n_levels} x {n_to_commit} polynomials to {curve_name}" + )); + for (level, blinded_node) in witness.into_iter().zip(blinded_path.into_iter()) { + // let t_commit_level = start_timer!(|| format!("Committing {n_to_commit} polynomials")); + let piop: P::ProverCircuit = +

>::prover_circuit(&self.piop_params, level.clone()); + let result = + >>::result(&piop); + debug_assert_eq!(result, blinded_node); let (pcs_openings, piop_proof, _transcript) = plonk_prover.reduce_to_pcs_opening(piop); piop_proofs.push(piop_proof); let PcsOpeningAt2Points { @@ -90,33 +123,131 @@ impl>::open_many_hiding( &self.pcs_params, - &polys, - &bfs, - &coords, + &polys_to_open, + &with_bfs, + &at_coords, &mut todo.clone(), rng, ); + end_timer!(t_open); let proof = CycleSideProof { piop_proofs, pcs_proof, todo, - fixed_columns_committed, + }; + proof + } + + pub fn batch_prove_side( + &self, + _blinded_path: Vec>, // TODO: probably not required + witness: [LevelWitnessWithBlinding>; L], + rng: &mut R, + ) -> BatchSideProof { + let curve_name = &std::any::type_name::()[53..]; + // println!("\n\nprover {curve_name}\nchildren={blinded_path:?}\n"); + + let n_columns = P::VerifierCircuit::N_COLUMNS; + let n_to_commit = L * n_columns + 3; // columns for multiple levels + the shared quotient chunks + let n_to_open = L * n_columns + 2; // --//-- + the (folded) quotient + the linearization polynomial + + let plonk_prover = PlonkProver::, _>::init( + self.pcs_params.ck(), + (), // TODO: + ArkTranscript::new(b"pasta-tree-level-proof"), + ); + + let parent_bfs: Vec<_> = witness.iter().map(|level| level.parent_bf).collect(); + let batch_piop = witness.map(|level| self.piop_params.prover_circuit(level)); + let batch_piop = BatchProver(batch_piop, PhantomData, PhantomData); + + let t_commit_side = start_timer!(|| format!( + "Committing {L}x{n_columns}+3 = {n_to_commit} polynomials to {curve_name}" + )); + let (pcs_openings, piop_proof, _transcript) = + plonk_prover.reduce_to_pcs_opening(batch_piop); + end_timer!(t_commit_side); + + let PcsOpeningAt2Points { + polys_at_zeta, + polys_at_zeta_omega, + zeta, + zeta_omega, + } = pcs_openings; + // println!("zeta = {zeta}\nq(zeta) = {}\n", polys_at_zeta[polys_at_zeta.len() - 1].evaluate(&zeta)); + + let mut at_coords = vec![BTreeSet::from([zeta]); polys_at_zeta.len()]; + let mut polys_to_open = polys_at_zeta; + at_coords.extend(vec![ + BTreeSet::from([zeta_omega]); + polys_at_zeta_omega.len() + ]); + polys_to_open.extend(polys_at_zeta_omega.clone()); + assert_eq!(polys_to_open.len(), n_to_open); + + let mut with_bfs: Vec<_> = parent_bfs + .into_iter() + .flat_map(|bf| vec![bf, C::ScalarField::zero(), C::ScalarField::zero()]) + .collect(); + with_bfs.resize(n_to_open, C::ScalarField::zero()); + + // use ark_ec::AffineRepr; + // for (i, ((p, z), bf)) in polys_to_open.iter() + // .zip(at_coords.iter().map(|z| z.first().unwrap())) + // .zip(with_bfs.iter()) + // .enumerate() { + // let v = p.evaluate(z); + // let c = HidingIpa::::commit(&self.pcs_params, &p).unwrap().0; + // println!("{i}: z={:.5}, v={:.5}, c={:.5}, bf={:.5}", z.to_string(), v.to_string(), c.x().unwrap().to_string(), bf.to_string()); + // } + + let t_open = start_timer!(|| format!( + "Opening {L}x{n_columns}+2 = {n_to_open} polynomials, max_degree = {}", + polys_to_open.iter().map(|p| p.degree()).max().unwrap() + )); + let todo = Coeffs(C::ScalarField::rand(rng), C::ScalarField::rand(rng)); + let pcs_proof = Shplonk::>::open_many_hiding( + &self.pcs_params, + &polys_to_open, + &with_bfs, + &at_coords, + &mut todo.clone(), + rng, + ); + end_timer!(t_open); + + let proof = BatchSideProof { + piop_proof, + pcs_proof, + todo, }; proof } diff --git a/pasta-tree/src/verifier.rs b/pasta-tree/src/verifier.rs index e0121b6..d073897 100644 --- a/pasta-tree/src/verifier.rs +++ b/pasta-tree/src/verifier.rs @@ -1,108 +1,114 @@ use crate::auth_path::blinded::BlindedAuthenticationPath; -use crate::{CurveTreeProof, CycleParams, CycleSide, CycleSideParams, CycleSideProof}; +use crate::{ + AffinePoint, CircuitParams, CurveModel, CycleParams, CycleSideParams, ProjectivePoint, +}; +use crate::{ArkTranscript, BatchSideProof, CurveTreeProof2}; +use crate::{CurveTreeProof, CycleSideProof}; use ark_ec::CurveGroup; -use ark_ec::short_weierstrass::{Affine, Projective, SWCurveConfig}; use ark_ff::PrimeField; +use std::marker::PhantomData; use w3f_pcs::pcs::PcsParams; +use w3f_pcs::pcs::commitment::WrappedAffine; use w3f_pcs::pcs::ipa::hiding::HidingIpa; use w3f_pcs::shplonk::Shplonk; +use w3f_plonk_common::batch::BatchVerifier; +use w3f_plonk_common::piop::VerifierPiop; use w3f_plonk_common::verifier::{PcsOpeningAt2Points, PlonkVerifier}; -use w3f_ring_proof::ArkTranscript; -use w3f_ring_proof::piop::verifier::PiopVerifier; -impl CycleParams, Projective> +impl CycleParams where - F0: PrimeField, - F1: PrimeField, - C0: SWCurveConfig, - C1: SWCurveConfig, + C0: CurveModel, + C1: CurveModel, + P0: CircuitParams, C1>, + P1: CircuitParams, C0>, { pub fn verify( &self, - auth_path: BlindedAuthenticationPath, Projective>, - proof: CurveTreeProof, Projective>, - _root: CycleSide, Affine>, + auth_path: BlindedAuthenticationPath, ProjectivePoint>, + proof: CurveTreeProof, + root: AffinePoint, ) -> bool { - // println!("leaf = {}", auth_path.c0_path[0]); - // println!("root = {:?}", root); - let _c0_x_coords: Vec> = proof - .c0_proof - .fixed_columns_committed - .iter() - .map(|c| c.points[0].0) - .collect(); - let _c1_x_coords: Vec> = proof - .c1_proof - .fixed_columns_committed - .iter() - .map(|c| c.points[0].0) - .collect(); - // match root { - // CycleSide::C0(c0_root) => { - // assert_eq!(c0_root, c0_x_coords[c0_x_coords.len() - 1]); - // assert_eq!(auth_path.c1_path, c1_x_coords); - // assert_eq!(auth_path.c0_path[1..], c0_x_coords[..c0_x_coords.len() - 1]); - // } - // CycleSide::C1(c1_root) => { - // assert_eq!(c1_root, c1_x_coords[c1_x_coords.len() - 1]); - // assert_eq!(auth_path.c1_path, c1_x_coords[..c1_x_coords.len() - 1]); - // assert_eq!(auth_path.c0_path[1..], c0_x_coords); - // } - // } + let BlindedAuthenticationPath { c0_path, c1_path } = auth_path; + let mut c0_parents = c0_path[1..].to_vec(); + c0_parents.push(root); + let c0_proof = self + .c0_params + .verify_side(c1_path.clone(), c0_parents, proof.c0_proof); + assert!(c0_proof); + let c1_proof = self.c1_params.verify_side(c0_path, c1_path, proof.c1_proof); + assert!(c1_proof); + c0_proof && c1_proof + } + + pub fn batch_verify( + &self, + auth_path: BlindedAuthenticationPath, ProjectivePoint>, + proof: CurveTreeProof2, + root: AffinePoint, + ) -> bool { + let BlindedAuthenticationPath { c0_path, c1_path } = auth_path; + let mut c0_parents = c0_path[1..].to_vec(); + c0_parents.push(root); let c0_proof = self .c0_params - .verify_side(auth_path.c1_path, proof.c0_proof); + .verify_batch(c1_path.clone(), c0_parents, proof.c0_proof); assert!(c0_proof); let c1_proof = self .c1_params - .verify_side(auth_path.c0_path, proof.c1_proof); + .verify_batch(c0_path, c1_path, proof.c1_proof); assert!(c1_proof); c0_proof && c1_proof } } -impl> - CycleSideParams> +impl, P: CircuitParams> + CycleSideParams { pub fn verify_side( &self, - blinded_path: Vec>, - side_proof: CycleSideProof, + // selected re-randomized children + children: Vec>, + // parents, re-randomized at the previous step + parents: Vec, + side_proof: CycleSideProof, ) -> bool { + // let curve_name = &std::any::type_name::()[53..]; + // println!("\n\nverifier {curve_name}\nchildren={children:?}\nparents={parents:?}\n"); + + // number of tree levels on this side + let n_levels = side_proof.piop_proofs.len(); + // per tree level + let n_to_open = P::VerifierCircuit::N_COLUMNS + 2; // plus the (folded) quotient (chunks) and the linearization polynomial + // per side + let n_openings = n_levels * n_to_open; + + let mut polys_to_open = Vec::with_capacity(n_openings); + let mut at_coords = Vec::with_capacity(n_openings); + let mut to_values = Vec::with_capacity(n_openings); + let plonk_verifier: PlonkVerifier, _> = PlonkVerifier::init( self.pcs_params.vk(), - &blinded_path, + &(), // TODO ArkTranscript::new(b"pasta-tree-level-proof"), ); - let mut polys = Vec::with_capacity(side_proof.piop_proofs.len() * 9); - let mut coords = Vec::with_capacity(side_proof.piop_proofs.len() * 9); - let mut vals = Vec::with_capacity(side_proof.piop_proofs.len() * 9); + //TODO: precompute + let fixed_cols = self.commit_fixed_columns(); - for ((blinded_node, piop_proof), parent) in blinded_path - .iter() + for ((child, parent), level_proof) in children + .into_iter() + .zip(parents.into_iter()) .zip(side_proof.piop_proofs.into_iter()) - .zip(side_proof.fixed_columns_committed.into_iter()) { - let (challenges, _rng) = plonk_verifier.restore_challenges( - blinded_node, - &piop_proof, - // '1' accounts for the quotient polynomial that is aggregated together with the columns - 8, - 7, - ); - let seed = self.piop_params.seed; - let seed_plus_result = (seed + blinded_node).into_affine(); - let domain_at_zeta = self.piop_params.domain.evaluate(challenges.zeta); - let piop = PiopVerifier::<_, _, Affine>::init( - domain_at_zeta, - parent, - piop_proof.column_commitments.clone(), - piop_proof.columns_at_zeta.clone(), - (seed.x, seed.y), - (seed_plus_result.x, seed_plus_result.y), + let challenges = plonk_verifier + .restore_fs_challenges::(&child, &level_proof); + let piop = self.piop_params.verifier_circuit( + (child, parent), + &fixed_cols, + level_proof.column_commitments.clone(), + level_proof.columns_at_zeta.clone(), + challenges.zeta, ); - let PcsOpeningAt2Points { open_at_zeta, open_at_zeta_omega, @@ -110,23 +116,118 @@ impl>::verify_many( &self.pcs_params.vk(), - &polys, + &polys_to_open, + side_proof.pcs_proof, + &at_coords, + &to_values, + &mut todo, + ); + valid + } + + pub fn verify_batch( + &self, + // selected re-randomized children + children: Vec>, + // parents, re-randomized at the previous step + parents: Vec, + side_proof: BatchSideProof, + ) -> bool { + // let curve_name = &std::any::type_name::()[53..]; + // println!("\n\nverifier {curve_name}\nchildren={children:?}\nparents={parents:?}\n"); + + let fixed_cols = self.commit_fixed_columns(); // TODO: precompute + let piop_proof = side_proof.piop_proof.clone(); + let instance: [AffinePoint; L] = children.clone().try_into().unwrap(); + + let plonk_verifier: PlonkVerifier, _> = PlonkVerifier::init( + self.pcs_params.vk(), + &(), // TODO + ArkTranscript::new(b"pasta-tree-level-proof"), + ); + + let challenges = plonk_verifier.restore_fs_challenges::, + P::VerifierCircuit, + L, + >, _, _>(&instance, &piop_proof); + let zeta_ = challenges.zeta; + // println!("zeta = {zeta_}"); + + let batch_piop: [_; L] = children + .into_iter() + .zip(parents.into_iter()) + .zip(piop_proof.column_commitments.into_iter()) + .zip(piop_proof.columns_at_zeta.into_iter()) + .map(|(((child, parent), cols), evals)| { + self.piop_params.verifier_circuit( + (child, parent), + &fixed_cols, + cols, + evals, + challenges.zeta, + ) + }) + .collect::>() + .try_into() + .unwrap_or_else(|_| panic!("wtf")); + let batch_piop = BatchVerifier(batch_piop, PhantomData, PhantomData); + + let PcsOpeningAt2Points { + open_at_zeta, + open_at_zeta_omega, + zeta, + zeta_omega, + vals_at_zeta, + vals_at_zeta_omega, + } = plonk_verifier.evaluate_piop(batch_piop, side_proof.piop_proof, challenges); + debug_assert_eq!(zeta, zeta_); + // println!("q(zeta) = {}", vals_at_zeta[vals_at_zeta.len() - 1]); + + let mut at_coords = vec![vec![zeta]; open_at_zeta.len()]; + let mut polys_to_open = open_at_zeta; + at_coords.extend(vec![vec![zeta_omega]; open_at_zeta_omega.len()]); + polys_to_open.extend(open_at_zeta_omega.clone()); + let to_values: Vec> = vals_at_zeta + .into_iter() + .chain(vals_at_zeta_omega.into_iter()) + .map(|v| vec![v]) + .collect(); + + // use ark_ec::AffineRepr; + // for (i, ((c, z), v)) in polys_to_open.iter() + // .zip(at_coords.iter().map(|z| z.first().unwrap())) + // .zip(to_values.iter().map(|v| v.first().unwrap())) + // .enumerate() { + // println!("{i}: z={:.5}, v={:.5}, c = {:.5}", z.to_string(), v.to_string(), c.0.x().unwrap().to_string()); + // } + + let mut todo = side_proof.todo; + let valid = Shplonk::>::verify_many( + &self.pcs_params.vk(), + &polys_to_open, side_proof.pcs_proof, - &coords, - &vals, + &at_coords, + &to_values, &mut todo, ); valid diff --git a/w3f-plonk-common/src/batch.rs b/w3f-plonk-common/src/batch.rs new file mode 100644 index 0000000..cae0453 --- /dev/null +++ b/w3f-plonk-common/src/batch.rs @@ -0,0 +1,164 @@ +use crate::domain::{Domain, EvaluatedDomain}; +use crate::piop::{ProverPiop, VerifierPiop}; +use crate::{ColumnsCommited, ColumnsEvaluated}; +use ark_ff::PrimeField; +use ark_poly::univariate::DensePolynomial; +use ark_poly::Evaluations; +use ark_std::marker::PhantomData; +use ark_std::vec::Vec; +use w3f_pcs::pcs::Commitment; + +impl, CC: ColumnsCommited, const K: usize> + ColumnsCommited for [CC; K] +{ + fn to_vec(self) -> Vec { + self.into_iter().flat_map(|p| p.to_vec()).collect() + } +} + +// TODO: remove +// TODO: fixed columns that are fixed (selectors) don't have to be re-evaluated in the same point. +// TODO: other columns (like root layer) can be just witnessed in the proof. +// Flattens the vec while moving the first 3 elements of each inner vec to the beginning of the vec. +fn excerpt_fixed_columns(column_vecs: Vec>) -> Vec { + let k = column_vecs.len(); + let total_cols = 9; + let fixed_cols = 3; + let advice_cols = total_cols - fixed_cols; + let mut fixed = Vec::with_capacity(fixed_cols * k); + let mut advice = Vec::with_capacity(advice_cols * k); + for mut vec in column_vecs { + assert_eq!(vec.len(), total_cols); + advice.extend(vec.split_off(3)); + fixed.extend(vec); + } + [fixed, advice].concat() +} + +impl, const K: usize> ColumnsEvaluated for [CE; K] { + fn to_vec(self) -> Vec { + let column_vecs = self + .iter() + .map(|evals| evals.clone().to_vec()) + .collect::>(); + let res = excerpt_fixed_columns(column_vecs); + let total_cols = 9; + debug_assert_eq!(res.len(), total_cols * K); + res + } +} + +pub struct BatchProver, P: ProverPiop, const K: usize>( + pub [P; K], + pub PhantomData, + pub PhantomData, +); +pub struct BatchVerifier, V: VerifierPiop, const K: usize>( + pub [V; K], + pub PhantomData, + pub PhantomData, +); + +impl, P: ProverPiop, const K: usize> ProverPiop + for BatchProver +{ + const N_COLUMNS: usize = P::N_COLUMNS * K; + const N_CONSTRAINTS: usize = P::N_CONSTRAINTS * K; + const N_QUOTIENT_CHUNKS: usize = P::N_QUOTIENT_CHUNKS; + type Commitments = [P::Commitments; K]; + type Evaluations = [P::Evaluations; K]; + type Instance = [P::Instance; K]; + + fn committed_columns) -> C + Clone>( + &self, + commit: Fun, + ) -> Self::Commitments { + self.0 + .iter() + .map(|p| p.committed_columns(commit.clone())) + .collect::>() + .try_into() + .unwrap() + } + + fn columns(&self) -> Vec> { + let column_vecs = self.0.iter().map(|p| p.columns()).collect::>(); + excerpt_fixed_columns(column_vecs) + } + + fn columns_evaluated(&self, zeta: &F) -> Self::Evaluations { + self.0 + .iter() + .map(|p| p.columns_evaluated(zeta)) + .collect::>() + .try_into() + .unwrap() + } + + fn constraints(&self) -> Vec> { + self.0.iter().flat_map(|p| p.constraints()).collect() + } + + fn constraints_lin(&self, zeta: &F) -> Vec> { + self.0 + .iter() + .flat_map(|p| p.constraints_lin(zeta)) + .collect() + } + + fn quotient(&self, alphas: &[F]) -> Option>> { + self._quotient_chunks(alphas) + } + + fn domain(&self) -> &Domain { + self.0[0].domain() + } + + fn result(&self) -> Self::Instance { + self.0 + .iter() + .map(|p| p.result()) + .collect::>() // Requires allocation if you use .collect() + .try_into() + .unwrap() + } +} + +impl, V: VerifierPiop, const K: usize> VerifierPiop + for BatchVerifier +{ + const N_COLUMNS: usize = V::N_COLUMNS * K; + const N_CONSTRAINTS: usize = V::N_CONSTRAINTS * K; + type Instance = [V::Instance; K]; + + fn precommitted_columns(&self) -> Vec { + self.0 + .iter() + .flat_map(|p| p.precommitted_columns()) + .collect() + } + + fn evaluate_constraints_main(&self) -> Vec { + self.0 + .iter() + .flat_map(|p| p.evaluate_constraints_main()) + .collect() + } + + fn lin_poly_commitment(&self, agg_coeffs: &[F]) -> (Vec, Vec) { + self.0 + .iter() + .zip(agg_coeffs.chunks(V::N_CONSTRAINTS)) + .map(|(p, alphas)| p.lin_poly_commitment(alphas)) + .reduce(|(mut acc_f, mut acc_c), (f, c)| { + acc_f.extend(f); + acc_c.extend(c); + (acc_f, acc_c) + }) + .unwrap() + } + + fn domain_evaluated(&self) -> &EvaluatedDomain { + self.0[0].domain_evaluated() + } +} diff --git a/w3f-plonk-common/src/domain.rs b/w3f-plonk-common/src/domain.rs index cd3dcb0..355788a 100644 --- a/w3f-plonk-common/src/domain.rs +++ b/w3f-plonk-common/src/domain.rs @@ -23,7 +23,7 @@ impl Domains { Self { x1, x4 } } - fn column_from_evals(&self, padded_evals: Vec, payload_len: usize) -> FieldColumn { + pub fn column_from_evals(&self, padded_evals: Vec, payload_len: usize) -> FieldColumn { debug_assert_eq!(padded_evals.len(), self.x1.size()); let evals = Evaluations::from_vec_and_domain(padded_evals, self.x1); let poly = evals.interpolate_by_ref(); @@ -226,6 +226,7 @@ pub struct EvaluatedDomain { pub l_first: F, pub l_last: F, pub vanishing_polynomial_inv: F, + pub z_n: F, // z^N } impl EvaluatedDomain { @@ -264,6 +265,7 @@ impl EvaluatedDomain { l_first, l_last, vanishing_polynomial_inv, + z_n, } } diff --git a/w3f-plonk-common/src/gadgets/booleanity.rs b/w3f-plonk-common/src/gadgets/booleanity.rs index 08b9363..3387b35 100644 --- a/w3f-plonk-common/src/gadgets/booleanity.rs +++ b/w3f-plonk-common/src/gadgets/booleanity.rs @@ -6,7 +6,7 @@ use ark_std::{vec, vec::Vec}; use crate::cond_select::{bit_to_field, CondSelect}; use crate::domain::Domain; -use crate::gadgets::VerifierGadget; +use crate::gadgets::{ProverGadget, VerifierGadget}; use crate::{const_evals, Column, FieldColumn}; #[derive(Clone)] @@ -52,12 +52,18 @@ pub struct Booleanity { bits: BitColumn, } -impl<'a, F: FftField> Booleanity { +impl Booleanity { pub fn init(bits: BitColumn) -> Self { Self { bits } } +} + +impl ProverGadget for Booleanity { + fn witness_columns(&self) -> Vec> { + todo!() + } - pub fn constraints(&self) -> Vec> { + fn constraints(&self) -> Vec> { let mut c = const_evals(F::one(), self.bits.domain_4x()); // c = 1 let b = &self.bits.col.evals_4x; c -= b; // c = 1 - b @@ -65,9 +71,13 @@ impl<'a, F: FftField> Booleanity { vec![c] } - pub fn constraints_linearized(&self, _z: &F) -> Vec> { + fn constraints_linearized(&self, _z: &F) -> Vec> { vec![DensePolynomial::zero()] } + + fn domain(&self) -> GeneralEvaluationDomain { + todo!() + } } pub struct BooleanityValues { diff --git a/w3f-plonk-common/src/gadgets/ec/sw_cond_add.rs b/w3f-plonk-common/src/gadgets/ec/sw_cond_add.rs index f5eb5b6..5c62f1e 100644 --- a/w3f-plonk-common/src/gadgets/ec/sw_cond_add.rs +++ b/w3f-plonk-common/src/gadgets/ec/sw_cond_add.rs @@ -1,17 +1,51 @@ use ark_ec::short_weierstrass::{Affine, SWCurveConfig}; -use ark_ff::{FftField, Field}; +use ark_ec::AffineRepr; +use ark_ff::{FftField, Field, Zero}; use ark_poly::univariate::DensePolynomial; use ark_poly::{Evaluations, GeneralEvaluationDomain}; use ark_std::{vec, vec::Vec}; -use crate::gadgets::ec::{CondAdd, CondAddValues}; +use crate::gadgets::ec::{AffineColumn, CondAdd, CondAddValues}; use crate::gadgets::{ProverGadget, VerifierGadget}; use crate::{const_evals, Column}; -impl ProverGadget for CondAdd> -where - F: FftField, - Curve: SWCurveConfig, +impl> ProverGadget + for AffineColumn> +{ + fn witness_columns(&self) -> Vec> { + todo!() + } + + // y^2 = x^3 + ax + b + fn constraints(&self) -> Vec> { + let domain = self.xs.domain_4x(); + let sw_coeff_a = &const_evals(Curve::COEFF_A, domain); + let sw_coeff_b = &const_evals(Curve::COEFF_B, domain); + let x = &self.xs.evals_4x; + let y = &self.ys.evals_4x; + let c = &(&(y * y) - &(&(x * x) * x)) - &(&(sw_coeff_a * x) + &sw_coeff_b); + vec![c] + } + + fn constraints_linearized(&self, _zeta: &F) -> Vec> { + vec![DensePolynomial::zero()] + } + + fn domain(&self) -> GeneralEvaluationDomain { + todo!() + } +} + +impl> VerifierGadget for Affine { + fn evaluate_constraints_main(&self) -> Vec { + let (x, y) = self.xy().unwrap(); + let c = y * y - x * x * x - C::COEFF_A * x - C::COEFF_B; + vec![c] + } +} + +impl> ProverGadget + for CondAdd> { fn witness_columns(&self) -> Vec> { vec![self.acc.xs.poly.clone(), self.acc.ys.poly.clone()] diff --git a/w3f-plonk-common/src/gadgets/equal_cells.rs b/w3f-plonk-common/src/gadgets/equal_cells.rs new file mode 100644 index 0000000..6b5c188 --- /dev/null +++ b/w3f-plonk-common/src/gadgets/equal_cells.rs @@ -0,0 +1,140 @@ +use ark_ff::{FftField, Field, Zero}; +use ark_poly::univariate::DensePolynomial; +use ark_poly::{Evaluations, GeneralEvaluationDomain}; + +use ark_std::{vec, vec::Vec}; + +use crate::domain::Domain; +use crate::gadgets::{ProverGadget, VerifierGadget}; +use crate::{Column, FieldColumn}; + +pub struct CellsEqPolys { + a: FieldColumn, + b: FieldColumn, + li: FieldColumn, +} + +pub struct EqualCells { + pub a: F, + pub b: F, + pub li: F, +} + +impl CellsEqPolys { + pub fn first_cells(a: FieldColumn, b: FieldColumn, domain: &Domain) -> Self { + Self::cells(a, b, 0, domain.l_first.clone(), domain) + } + + pub fn last_cells(a: FieldColumn, b: FieldColumn, domain: &Domain) -> Self { + Self::cells(a, b, domain.capacity - 1, domain.l_last.clone(), domain) + } + + pub fn cells( + a: FieldColumn, + b: FieldColumn, + i: usize, + li: FieldColumn, + domain: &Domain, + ) -> Self { + assert_eq!(a.payload_len(), domain.capacity); + assert_eq!(b.payload_len(), domain.capacity); + assert_eq!(a.evals.evals[i], b.evals.evals[i]); + Self { a, b, li } + } + + pub fn first_constraints( + a: FieldColumn, + b: FieldColumn, + domain: &Domain, + ) -> Vec> { + let gadget = Self::first_cells(a, b, domain); + gadget.constraints() + } + + pub fn last_constraints( + a: FieldColumn, + b: FieldColumn, + domain: &Domain, + ) -> Vec> { + let gadget = Self::last_cells(a, b, domain); + gadget.constraints() + } + + pub fn constraints_lin() -> Vec> { + vec![DensePolynomial::zero()] + } +} + +impl ProverGadget for CellsEqPolys { + fn witness_columns(&self) -> Vec> { + todo!() + } + + fn constraints(&self) -> Vec> { + let a = &self.a.evals_4x; + let b = &self.b.evals_4x; + let li = &self.li.evals_4x; + let c = li * &(a - b); + vec![c] + } + + fn constraints_linearized(&self, _z: &F) -> Vec> { + Self::constraints_lin() + } + + fn domain(&self) -> GeneralEvaluationDomain { + todo!() + } +} + +impl VerifierGadget for EqualCells { + fn evaluate_constraints_main(&self) -> Vec { + let c = self.li * (self.a - self.b); + vec![c] + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_helpers::random_vec; + use ark_ed_on_bls12_381_bandersnatch::Fq; + use ark_poly::Polynomial; + use ark_std::test_rng; + + fn _test_equal_cells_gadget(zk_rows: usize) { + let rng = &mut test_rng(); + + let log_n = 8; + let n = 1 << log_n; + let domain = Domain::with_zk_rows(n, zk_rows); + + let a = random_vec(domain.capacity, rng); + let mut b = random_vec(domain.capacity, rng); + b[0] = a[0]; + let a = domain.column(a); + let b = domain.column(b); + + let constraints_first = CellsEqPolys::::first_constraints(a, b, &domain); + let constraint_poly = constraints_first[0].interpolate_by_ref(); + assert_eq!(constraint_poly.degree(), 2 * n - 2); + assert!(domain.compute_quotient(&constraint_poly).is_some()); + + let a = random_vec(domain.capacity, rng); + let mut b = random_vec(domain.capacity, rng); + b[domain.capacity - 1] = a[domain.capacity - 1]; + let a = domain.column(a); + let b = domain.column(b); + + let constraints_last = CellsEqPolys::::last_constraints(a, b, &domain); + let constraint_poly = constraints_last[0].interpolate_by_ref(); + assert_eq!(constraint_poly.degree(), 2 * n - 2); + assert!(domain.compute_quotient(&constraint_poly).is_some()); + } + + #[test] + fn test_equal_cells_gadget() { + _test_equal_cells_gadget(0); + _test_equal_cells_gadget(3); + } +} diff --git a/w3f-plonk-common/src/gadgets/fixed_cells.rs b/w3f-plonk-common/src/gadgets/fixed_cells.rs index 35eec37..96572ad 100644 --- a/w3f-plonk-common/src/gadgets/fixed_cells.rs +++ b/w3f-plonk-common/src/gadgets/fixed_cells.rs @@ -1,27 +1,24 @@ use ark_ff::{FftField, Field, Zero}; use ark_poly::univariate::DensePolynomial; -use ark_poly::Evaluations; +use ark_poly::{Evaluations, GeneralEvaluationDomain}; use ark_std::{vec, vec::Vec}; use crate::domain::Domain; -use crate::gadgets::VerifierGadget; +use crate::gadgets::{ProverGadget, VerifierGadget}; use crate::{const_evals, Column, FieldColumn}; pub struct FixedCells { col: FieldColumn, - l_first: FieldColumn, - l_last: FieldColumn, - col_first: F, - col_last: F, + i: Vec, + l_i: Vec>, + col_i: Vec, } pub struct FixedCellsValues { pub col: F, - pub col_first: F, - pub col_last: F, - pub l_first: F, - pub l_last: F, + pub l_i: Vec, + pub col_i: Vec, } impl FixedCells { @@ -29,31 +26,50 @@ impl FixedCells { debug_assert_eq!(col.payload_len(), domain.capacity); let col_first = col.evals[0]; let col_last = col.evals[domain.capacity - 1]; - Self::init(col, domain, col_first, col_last) + Self::first_and_last(col, domain, col_first, col_last) } pub fn init(col: FieldColumn, domain: &Domain, col_first: F, col_last: F) -> Self { + Self::first_and_last(col, domain, col_first, col_last) + } + + pub fn first_and_last( + col: FieldColumn, + domain: &Domain, + col_first: F, + col_last: F, + ) -> Self { debug_assert_eq!(col.payload_len(), domain.capacity); let l_first = domain.l_first.clone(); let l_last = domain.l_last.clone(); Self { col, - l_first, - l_last, - col_first, - col_last, + i: vec![0, domain.capacity - 1], + l_i: vec![l_first, l_last], + col_i: vec![col_first, col_last], } } - pub fn constraints(&self) -> Vec> { - let domain_capacity = self.col.payload_len(); // that's an ugly way to learn the capacity, but we've asserted it above. - let c = &Self::constraint_cell(&self.col, &self.l_first, 0, self.col_first) - + &Self::constraint_cell(&self.col, &self.l_last, domain_capacity - 1, self.col_last); - vec![c] + pub fn first(col: FieldColumn, domain: &Domain, col_first: F) -> Self { + debug_assert_eq!(col.payload_len(), domain.capacity); + let l_first = domain.l_first.clone(); + Self { + col, + i: vec![0], + l_i: vec![l_first], + col_i: vec![col_first], + } } - pub fn constraints_linearized(&self, _z: &F) -> Vec> { - vec![DensePolynomial::zero()] + pub fn last(col: FieldColumn, domain: &Domain, col_last: F) -> Self { + debug_assert_eq!(col.payload_len(), domain.capacity); + let l_last = domain.l_last.clone(); + Self { + col, + i: vec![domain.capacity - 1], + l_i: vec![l_last], + col_i: vec![col_last], + } } /// Constraints the column `col` to have the value `col[i]` at index `i`. @@ -74,6 +90,34 @@ impl FixedCells { } } +impl ProverGadget for FixedCells { + fn witness_columns(&self) -> Vec> { + todo!() + } + + fn constraints(&self) -> Vec> { + let c = self + .i + .iter() + .zip(self.l_i.iter()) + .zip(self.col_i.iter()) + .map(|((i, l_i), col_i)| Self::constraint_cell(&self.col, l_i, *i, *col_i)) + .reduce(|acc, c| &acc + &c) + .unwrap(); + // let c = &Self::constraint_cell(&self.col, &self.l_first, 0, self.col_first) + // + &Self::constraint_cell(&self.col, &self.l_last, domain_capacity - 1, self.col_last); + vec![c] + } + + fn constraints_linearized(&self, _z: &F) -> Vec> { + vec![DensePolynomial::zero()] + } + + fn domain(&self) -> GeneralEvaluationDomain { + todo!() + } +} + impl FixedCellsValues { pub fn evaluate_for_cell(col_eval: F, li_eval: F, cell_val: F) -> F { li_eval * (col_eval - cell_val) @@ -82,8 +126,12 @@ impl FixedCellsValues { impl VerifierGadget for FixedCellsValues { fn evaluate_constraints_main(&self) -> Vec { - let c = Self::evaluate_for_cell(self.col, self.l_first, self.col_first) - + Self::evaluate_for_cell(self.col, self.l_last, self.col_last); + let c = self + .l_i + .iter() + .zip(self.col_i.iter()) + .map(|(l_i, col_i)| Self::evaluate_for_cell(self.col, *l_i, *col_i)) + .sum(); vec![c] } } diff --git a/w3f-plonk-common/src/gadgets/inner_prod_inv.rs b/w3f-plonk-common/src/gadgets/inner_prod_inv.rs new file mode 100644 index 0000000..b05c3d6 --- /dev/null +++ b/w3f-plonk-common/src/gadgets/inner_prod_inv.rs @@ -0,0 +1,140 @@ +use ark_ff::{FftField, Field}; +use ark_poly::univariate::DensePolynomial; +use ark_poly::{Evaluations, GeneralEvaluationDomain}; + +use ark_std::{vec, vec::Vec}; + +use crate::domain::Domain; +use crate::gadgets::{ProverGadget, VerifierGadget}; +use crate::{Column, FieldColumn}; + +/// Does the same as `inner_prod.rs`, but with the witness column reversed. +/// The input vectors keep the normal ordering. The witness column contains +/// the seed at `acc[domain.capacity - 1] = seed` +/// and the inner product result at `acc[0] = seed + `. +pub struct InnerProdInv { + a: FieldColumn, + b: FieldColumn, + not_last: FieldColumn, + pub acc: FieldColumn, +} + +pub struct InnerProdInvValues { + pub a: F, + pub b: F, + pub not_last: F, + pub acc: F, +} + +impl InnerProdInv { + pub fn init(a: FieldColumn, b: FieldColumn, domain: &Domain) -> Self { + // we need an extra slot to seed the partial inner products acc with `0`. + assert_eq!(a.payload_len(), domain.capacity - 1); + assert_eq!(b.payload_len(), domain.capacity - 1); + let inner_prods = Self::partial_inner_prods(a.payload(), b.payload()); + let mut acc = vec![F::zero()]; + acc.extend(inner_prods); + acc.reverse(); + let acc = domain.column(acc); + Self { + a, + b, + not_last: domain.not_last_row.clone(), + acc, + } + } + + /// Returns a[n-1]b[n-1], a[n-1]b[n-1] + a[n-2]b[n-2], ..., a[0]b[0] + a[1]b[1] + ... + a[n-1]b[n-1] + fn partial_inner_prods(a: &[F], b: &[F]) -> Vec { + assert_eq!(a.len(), b.len()); + a.iter() + .rev() + .zip(b.iter().rev()) + .scan(F::zero(), |state, (&a, b)| { + *state += a * b; + Some(*state) + }) + .collect() + } +} + +impl ProverGadget for InnerProdInv { + fn witness_columns(&self) -> Vec> { + vec![self.acc.poly.clone()] + } + + fn constraints(&self) -> Vec> { + let a = &self.a.evals_4x; + let b = &self.b.evals_4x; + let acc = &self.acc.evals_4x; + let acc_shifted = &self.acc.shifted_4x(); + let not_last = &self.not_last.evals_4x; + let c = &(&(acc - acc_shifted) - &(a * b)) * not_last; + vec![c] + } + + fn constraints_linearized(&self, _z: &F) -> Vec> { + let c = -(&self.acc.poly * self.not_last.evaluate(_z)); + vec![c] + } + + fn domain(&self) -> GeneralEvaluationDomain { + self.a.evals.domain() + } +} + +impl VerifierGadget for InnerProdInvValues { + fn evaluate_constraints_main(&self) -> Vec { + let c = (self.acc - self.a * self.b) * self.not_last; + vec![c] + } +} + +#[cfg(test)] +mod tests { + use ark_ed_on_bls12_381_bandersnatch::Fq; + use ark_ff::{Field, Zero}; + use ark_poly::Polynomial; + use ark_std::test_rng; + + use crate::domain::Domain; + use crate::test_helpers::random_vec; + + use super::*; + + fn inner_prod(a: &[F], b: &[F]) -> F { + assert_eq!(a.len(), b.len()); + a.iter().zip(b).map(|(a, b)| *a * b).sum() + } + + fn _test_inner_prod_inv_gadget(zk_rows: usize) { + let rng = &mut test_rng(); + + let log_n = 10; + let n = 2usize.pow(log_n); + let domain = Domain::with_zk_rows(n, zk_rows); + + let a = random_vec(domain.capacity - 1, rng); + let b = random_vec(domain.capacity - 1, rng); + let ab = inner_prod(&a, &b); + let a = domain.column(a); + let b = domain.column(b); + + let gadget = InnerProdInv::::init(a, b, &domain); + + let acc = &gadget.acc.evals.evals; + assert!(acc[domain.capacity - 1].is_zero()); + assert_eq!(acc[0], ab); + + let constraint_poly = gadget.constraints()[0].interpolate_by_ref(); + + assert_eq!(constraint_poly.degree(), 2 * n - 1); + assert!(domain.compute_quotient(&constraint_poly).is_some()); + } + + #[test] + fn test_inner_prod_inv_gadget() { + _test_inner_prod_inv_gadget(0); + _test_inner_prod_inv_gadget(3); + } +} diff --git a/w3f-plonk-common/src/gadgets/mod.rs b/w3f-plonk-common/src/gadgets/mod.rs index 9d8bdf2..9fd7d5d 100644 --- a/w3f-plonk-common/src/gadgets/mod.rs +++ b/w3f-plonk-common/src/gadgets/mod.rs @@ -7,8 +7,10 @@ pub mod booleanity; // pub mod inner_prod_pub; pub mod column_sum; pub mod ec; +pub mod equal_cells; pub mod fixed_cells; pub mod inner_prod; +pub mod inner_prod_inv; pub trait ProverGadget { // Columns populated by the gadget. diff --git a/w3f-plonk-common/src/kzg_acc.rs b/w3f-plonk-common/src/kzg_acc.rs index 0b7bbeb..306cb4c 100644 --- a/w3f-plonk-common/src/kzg_acc.rs +++ b/w3f-plonk-common/src/kzg_acc.rs @@ -1,6 +1,6 @@ use crate::piop::VerifierPiop; use crate::verifier::Challenges; -use crate::{ColumnsCommited, ColumnsEvaluated, Proof}; +use crate::{q_chunking, ColumnsCommited, ColumnsEvaluated, Proof}; use ark_ec::pairing::Pairing; use ark_ec::{CurveGroup, VariableBaseMSM}; use ark_ff::{PrimeField, Zero}; @@ -93,8 +93,8 @@ impl KzgAccumulator { let zeta_omega = zeta * piop.domain_evaluated().omega(); let lin_comm = piop.lin_poly_commitment(&challenges.alphas); - // Openning at `z` - // TODO: try to get rid of the commitment wrapper in flonk + // Openning at `z`. Columns and the quotient are aggregated using `nu`s. + let mut r_nus = challenges.nus.iter().map(|nu| r * nu); self.acc_points.extend( piop.precommitted_columns() .iter() @@ -109,9 +109,19 @@ impl KzgAccumulator { .map(|c| c.0) .collect::>(), ); - self.acc_points.push(proof.quotient_commitment.clone().0); self.acc_scalars - .extend(challenges.nus.iter().map(|nu| *nu * r).collect::>()); // numbers should match here + .extend(r_nus.by_ref().take(Piop::N_COLUMNS)); + + // quotient (chunks) at `z` + let r_nu_last = r_nus.next().unwrap(); + let z_n = piop.domain_evaluated().z_n; + self.acc_points + .extend(proof.quotient_chunks.iter().map(|c| c.0)); + self.acc_scalars.extend( + q_chunking::chunk_coeffs(z_n) + .map(|c| r_nu_last * c) + .take(proof.quotient_chunks.len()), + ); self.acc_points.push(proof.agg_at_zeta_proof); self.acc_scalars.push(zeta * r); diff --git a/w3f-plonk-common/src/lib.rs b/w3f-plonk-common/src/lib.rs index 18e2ee4..6174265 100644 --- a/w3f-plonk-common/src/lib.rs +++ b/w3f-plonk-common/src/lib.rs @@ -9,12 +9,14 @@ use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::{vec, vec::Vec}; use w3f_pcs::pcs::{Commitment, PCS}; +pub mod batch; pub mod cond_select; pub mod domain; pub mod gadgets; pub mod kzg_acc; pub mod piop; pub mod prover; +pub mod q_chunking; pub mod test_helpers; pub mod transcript; pub mod verifier; @@ -36,7 +38,7 @@ pub struct FieldColumn { // We require all the evaluations padded to the domain size // (as we need to add blinding cells aka zk_rows) at the end of the vector. // `payload_len` keeps the original length of the data. - payload_len: usize, + pub payload_len: usize, } impl FieldColumn { @@ -74,13 +76,13 @@ pub fn const_evals(c: F, domain: GeneralEvaluationDomain) -> Eva } pub trait ColumnsEvaluated: - Clone + CanonicalSerialize + CanonicalDeserialize + Clone + ark_std::fmt::Debug + CanonicalSerialize + CanonicalDeserialize { fn to_vec(self) -> Vec; } pub trait ColumnsCommited>: - Clone + CanonicalSerialize + CanonicalDeserialize + Clone + ark_std::fmt::Debug + CanonicalSerialize + CanonicalDeserialize { fn to_vec(self) -> Vec; } @@ -104,14 +106,14 @@ where { pub column_commitments: Commitments, pub columns_at_zeta: Evaluations, - pub quotient_commitment: CS::C, + pub quotient_chunks: Vec, pub lin_at_zeta_omega: F, pub agg_at_zeta_proof: CS::Proof, pub lin_at_zeta_omega_proof: CS::Proof, } /// Same as `Proof` but excluding the PCS opening. -#[derive(Clone, CanonicalSerialize, CanonicalDeserialize)] +#[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize)] pub struct PiopProof where F: PrimeField, @@ -121,7 +123,7 @@ where { pub column_commitments: Commitments, pub columns_at_zeta: Evaluations, - pub quotient_commitment: C, + pub quotient_chunks: Vec, pub lin_at_zeta_omega: F, } @@ -136,7 +138,7 @@ where PiopProof { column_commitments: self.column_commitments.clone(), columns_at_zeta: self.columns_at_zeta.clone(), - quotient_commitment: self.quotient_commitment.clone(), + quotient_chunks: self.quotient_chunks.clone(), lin_at_zeta_omega: self.lin_at_zeta_omega, } } diff --git a/w3f-plonk-common/src/piop.rs b/w3f-plonk-common/src/piop.rs index 5ca915f..792a118 100644 --- a/w3f-plonk-common/src/piop.rs +++ b/w3f-plonk-common/src/piop.rs @@ -1,23 +1,25 @@ +use crate::domain::{Domain, EvaluatedDomain}; +use crate::{q_chunking, ColumnsCommited, ColumnsEvaluated}; use ark_ff::{FftField, PrimeField}; use ark_poly::univariate::DensePolynomial; use ark_poly::Evaluations; +use ark_poly::Polynomial; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use ark_std::vec; use ark_std::vec::Vec; use w3f_pcs::pcs::Commitment; -use crate::domain::{Domain, EvaluatedDomain}; -use crate::{ColumnsCommited, ColumnsEvaluated}; - pub trait ProverPiop> { const N_COLUMNS: usize; const N_CONSTRAINTS: usize; + const N_QUOTIENT_CHUNKS: usize = 1; type Commitments: ColumnsCommited; type Evaluations: ColumnsEvaluated; - type Instance: CanonicalSerialize + CanonicalDeserialize; + type Instance: Clone + ark_std::fmt::Debug + CanonicalSerialize + CanonicalDeserialize; // Commitments to the column polynomials excluding the precommitted columns. - fn committed_columns) -> C>( + fn committed_columns) -> C + Clone>( &self, commit: Fun, ) -> Self::Commitments; @@ -32,7 +34,27 @@ pub trait ProverPiop> { // Constraint polynomials in evaluation form. fn constraints(&self) -> Vec>; - fn compute_quotient(&self, alphas: &[F]) -> Option> { + fn _quotient_chunks(&self, alphas: &[F]) -> Option>> { + let q = Self::_compute_quotient(self, alphas); + q.map(|q| { + let _q_deg = q.degree(); + let q_chunks = q_chunking::chunk_quotient(q, self.domain().domain_size()); + debug_assert_eq!(q_chunks.len(), Self::N_QUOTIENT_CHUNKS); + #[cfg(feature = "std")] + println!( + "Chunking deg {} polynomial into {} chunks", + _q_deg, + q_chunks.len() + ); + q_chunks + }) + } + + fn quotient(&self, alphas: &[F]) -> Option>> { + self._compute_quotient(alphas).map(|q| vec![q]) + } + + fn _compute_quotient(&self, alphas: &[F]) -> Option> { let constraints = self.constraints(); // Aggregate constraint polynomials in evaluation form... let agg_constraint = aggregate_evaluations(&constraints, &alphas); diff --git a/w3f-plonk-common/src/prover.rs b/w3f-plonk-common/src/prover.rs index b1049df..ba923e4 100644 --- a/w3f-plonk-common/src/prover.rs +++ b/w3f-plonk-common/src/prover.rs @@ -11,7 +11,7 @@ use w3f_pcs::pcs::PCS; use crate::piop::ProverPiop; use crate::transcript::PlonkTranscript; -use crate::{PiopProof, Proof}; +use crate::{q_chunking, PiopProof, Proof}; pub struct PlonkProver, T: PlonkTranscript> { // Polynomial commitment scheme committer's key. @@ -56,22 +56,44 @@ impl, T: PlonkTranscript> PlonkProver { let mut transcript = self.transcript_prelude.clone(); transcript.add_instance(&piop.result()); + // ROUND 1 // The prover commits to the columns. + let t_commit_cols = start_timer!(|| format!( + "Committing to {} degree-{} columns", + P::N_COLUMNS, + piop.domain().domain_size() - 1 + )); let column_commitments = piop.committed_columns(|p| CS::commit(&self.pcs_ck, p).unwrap()); transcript.add_committed_cols(&column_commitments); + end_timer!(t_commit_cols); // ROUND 2 - let alphas = transcript.get_constraints_aggregation_coeffs(P::N_CONSTRAINTS); - let quotient_poly = piop.compute_quotient(&alphas).unwrap(); // The prover commits to the quotient polynomial... - let quotient_commitment = CS::commit(&self.pcs_ck, "ient_poly).unwrap(); - transcript.add_quotient_commitment("ient_commitment); + let alphas = transcript.get_constraints_aggregation_coeffs(P::N_CONSTRAINTS); + let quotient_chunks = piop.quotient(&alphas).unwrap(); + let t_commit_q = start_timer!(|| format!( + "Committing to {} degree-{} quotient chunks", + quotient_chunks.len(), + quotient_chunks[0].degree() + )); + let quotient_chunks_committed: Vec<_> = quotient_chunks + .iter() + .map(|qi| CS::commit(&self.pcs_ck, qi).unwrap()) + .collect(); + for qi_committed in quotient_chunks_committed.iter() { + transcript.add_quotient_commitment(&qi_committed); + } + // let quotient_commitment = CS::commit(&self.pcs_ck, "ient_poly).unwrap(); + // transcript.add_quotient_commitment("ient_commitment); + end_timer!(t_commit_q); // and receives the evaluation point in response // ROUND 3 let zeta = transcript.get_evaluation_point(); + let z_n = zeta.pow([piop.domain().domain_size() as u64]); + let q_folded = q_chunking::fold_quotient_chunks("ient_chunks, z_n); let columns_to_open = piop.columns(); let columns_at_zeta = piop.columns_evaluated(&zeta); let constraint_polys_linearized = piop.constraints_lin(&zeta); @@ -82,11 +104,11 @@ impl, T: PlonkTranscript> PlonkProver transcript.add_evaluations(&columns_at_zeta, &lin_at_zeta_omega); let piop_proof = PiopProof { column_commitments, - quotient_commitment, + quotient_chunks: quotient_chunks_committed, columns_at_zeta, lin_at_zeta_omega, }; - let polys_at_zeta = [columns_to_open, vec![quotient_poly]].concat(); + let polys_at_zeta = [columns_to_open, vec![q_folded]].concat(); let pcs_openings = PcsOpeningAt2Points { polys_at_zeta, polys_at_zeta_omega: vec![lin], @@ -110,7 +132,7 @@ impl, T: PlonkTranscript> PlonkProver let lin = &polys_at_zeta_omega[0]; let PiopProof { column_commitments, - quotient_commitment, + quotient_chunks: quotient_commitment, columns_at_zeta, lin_at_zeta_omega, } = piop_proof; @@ -125,7 +147,7 @@ impl, T: PlonkTranscript> PlonkProver end_timer!(_t_open_zeta_omega); Proof { column_commitments, - quotient_commitment, + quotient_chunks: quotient_commitment, columns_at_zeta, lin_at_zeta_omega, agg_at_zeta_proof, diff --git a/w3f-plonk-common/src/q_chunking.rs b/w3f-plonk-common/src/q_chunking.rs new file mode 100644 index 0000000..31c9e3f --- /dev/null +++ b/w3f-plonk-common/src/q_chunking.rs @@ -0,0 +1,40 @@ +use ark_ff::{Field, PrimeField}; +use ark_poly::univariate::DensePolynomial; +use ark_poly::DenseUVPolynomial; +use ark_std::vec::Vec; +use w3f_pcs::pcs::Commitment; +use w3f_pcs::utils; + +pub fn chunk_quotient(q: DensePolynomial, n: usize) -> Vec> { + q.coeffs + .chunks(n) + .map(|coeffs| DensePolynomial::from_coefficients_slice(coeffs)) + .collect() +} + +pub fn chunk_coeffs(z_to_n: F) -> impl Iterator { + utils::powers(z_to_n) +} + +pub fn fold_quotient_chunks( + chunks: &[DensePolynomial], + z_to_n: F, +) -> DensePolynomial { + chunks + .iter() + .zip(chunk_coeffs(z_to_n)) + .map(|(chunk, coeff)| chunk * coeff) + .reduce(|acc, new| acc + new) + .unwrap() +} + +pub fn compose_quotient>(chunks: &[C], z_to_n: F) -> C { + chunks + .iter() + .zip(chunk_coeffs(z_to_n)) + .map(|(chunk, coeff)| chunk.mul(coeff)) + .sum() +} + +#[cfg(test)] +mod tests {} diff --git a/w3f-plonk-common/src/verifier.rs b/w3f-plonk-common/src/verifier.rs index 9d78234..a0dcab3 100644 --- a/w3f-plonk-common/src/verifier.rs +++ b/w3f-plonk-common/src/verifier.rs @@ -7,7 +7,7 @@ use w3f_pcs::pcs::{Commitment, PcsParams, PCS}; use crate::piop::VerifierPiop; use crate::transcript::PlonkTranscript; -use crate::{ColumnsCommited, ColumnsEvaluated, PiopProof, Proof}; +use crate::{q_chunking, ColumnsCommited, ColumnsEvaluated, PiopProof, Proof}; pub struct PlonkVerifier, T: PlonkTranscript> { // Polynomial commitment scheme verifier's key. @@ -54,7 +54,12 @@ impl, T: PlonkTranscript> PlonkVerifier, T: PlonkTranscript> PlonkVerifier> { pub(crate) bits: C, pub(crate) inn_prod_acc: C, @@ -40,7 +40,7 @@ impl> ColumnsCommited for RingCommitments< } } -#[derive(Clone, CanonicalSerialize, CanonicalDeserialize)] +#[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize)] pub struct RingEvaluations { pub(crate) points: [F; 2], pub(crate) ring_selector: F, @@ -208,3 +208,58 @@ pub fn index, G: AffineRepr>( }; (prover_key, verifier_key) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::index; + use crate::tests::setup; + use ark_ed_on_bls12_381_bandersnatch::{EdwardsAffine, Fq, Fr}; + use ark_std::{test_rng, UniformRand}; + use w3f_pcs::pcs::id::WrappedPolynomial; + use w3f_pcs::pcs::IdentityCommitment; + use w3f_pcs::Polynomial; + use w3f_plonk_common::piop::ProverPiop; + use w3f_plonk_common::test_helpers::random_vec; + + #[test] + fn test_ring_piop() { + let rng = &mut test_rng(); + + let log_n = 9; + let n = 1 << log_n; + + let (pcs_params, piop_params) = setup::<_, IdentityCommitment>(rng, n); + let pks = random_vec::(piop_params.keyset_part_size, rng); + let (prover_key, verifier_key) = + index::<_, IdentityCommitment, _>(&pcs_params, &piop_params, &pks); + let fixed_columns = prover_key.fixed_columns.clone(); + let prover: PiopProver = + PiopProver::build(&piop_params, fixed_columns, 1, Fr::rand(rng)); + assert!(ProverPiop::>::constraints_satisfied(&prover)); + + let zeta = Fq::rand(rng); + let columns = ProverPiop::>::columns(&prover); + let evals = ProverPiop::>::columns_evaluated(&prover, &zeta); + let evals = evals.to_vec(); + assert_eq!(columns.len(), evals.len()); + for (p, v) in columns.iter().zip(evals) { + assert_eq!(p.evaluate(&zeta), v); + } + + let fixed_columns = verifier_key.fixed_columns_committed.as_vec(); + let advice_columns = + ProverPiop::>::committed_columns(&prover, |p| { + IdentityCommitment::commit(&prover_key.pcs_ck, p).unwrap() + }); + let advice_columns = advice_columns.to_vec(); + let commitments = [fixed_columns, advice_columns].concat(); + assert_eq!(columns.len(), commitments.len()); + for (p, c) in columns.iter().zip(commitments) { + assert_eq!( + IdentityCommitment::commit(&prover_key.pcs_ck, p).unwrap(), + c + ); + } + } +} diff --git a/w3f-ring-proof/src/piop/prover.rs b/w3f-ring-proof/src/piop/prover.rs index eed0bf3..6244395 100644 --- a/w3f-ring-proof/src/piop/prover.rs +++ b/w3f-ring-proof/src/piop/prover.rs @@ -275,32 +275,3 @@ where self.cond_add.result() } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::index; - use crate::tests::setup; - use ark_ed_on_bls12_381_bandersnatch::{EdwardsAffine, Fq, Fr}; - use ark_std::{test_rng, UniformRand}; - use w3f_pcs::pcs::id::WrappedPolynomial; - use w3f_pcs::pcs::IdentityCommitment; - use w3f_plonk_common::test_helpers::random_vec; - - #[test] - fn test_constraints() { - let rng = &mut test_rng(); - - let log_n = 9; - let n = 1 << log_n; - - let (pcs_params, piop_params) = setup::<_, IdentityCommitment>(rng, n); - let pks = random_vec::(piop_params.keyset_part_size, rng); - let (prover_key, _verifier_key) = - index::<_, IdentityCommitment, _>(&pcs_params, &piop_params, &pks); - let fixed_columns = prover_key.fixed_columns.clone(); - let piop: PiopProver = - PiopProver::build(&piop_params, fixed_columns, 1, Fr::rand(rng)); - assert!(ProverPiop::>::constraints_satisfied(&piop)); - } -} diff --git a/w3f-ring-proof/src/piop/verifier.rs b/w3f-ring-proof/src/piop/verifier.rs index a5eb525..0a37f3e 100644 --- a/w3f-ring-proof/src/piop/verifier.rs +++ b/w3f-ring-proof/src/piop/verifier.rs @@ -67,26 +67,20 @@ impl, P: AffineRepr> PiopVerifier let cond_add_acc_x = FixedCellsValues { col: all_columns_evaluated.cond_add_acc[0], - col_first: init.0, - col_last: result.0, - l_first: domain_evals.l_first, - l_last: domain_evals.l_last, + l_i: vec![domain_evals.l_first, domain_evals.l_last], + col_i: vec![init.0, result.0], }; let cond_add_acc_y = FixedCellsValues { col: all_columns_evaluated.cond_add_acc[1], - col_first: init.1, - col_last: result.1, - l_first: domain_evals.l_first, - l_last: domain_evals.l_last, + l_i: vec![domain_evals.l_first, domain_evals.l_last], + col_i: vec![init.1, result.1], }; let inner_prod_acc = FixedCellsValues { col: all_columns_evaluated.inn_prod_acc, - col_first: F::zero(), - col_last: F::one(), - l_first: domain_evals.l_first, - l_last: domain_evals.l_last, + l_i: vec![domain_evals.l_first, domain_evals.l_last], + col_i: vec![F::zero(), F::one()], }; Self {