diff --git a/Cargo.toml b/Cargo.toml index 82d1c32..0baec55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,8 @@ [workspace] resolver = "2" members = [ - # "evm-vrfier", +# "evm-vrfier", + "pasta-tree", "w3f-plonk-common", "w3f-ring-proof", # "w3f-ring-vrf-snark", @@ -15,7 +16,7 @@ ark-poly = { version = "0.6", default-features = false } ark-serialize = { version = "0.6", default-features = false, features = ["derive"] } #w3f-pcs = { version = "0.0.6", default-features = false } #w3f-pcs = { path = "../fflonk", default-features = false } -w3f-pcs = { version = "0.0.6", git = "https://github.com/paritytech/fflonk/", 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.7", default-features = false } w3f-plonk-common = { path = "w3f-plonk-common", default-features = false } rayon = { version = "1", default-features = false } diff --git a/pasta-tree/Cargo.toml b/pasta-tree/Cargo.toml new file mode 100644 index 0000000..75d7a48 --- /dev/null +++ b/pasta-tree/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "pasta-tree" +version = "0.0.1" +edition = "2024" +authors = ["Sergey Vasilyev "] +license = "MIT/Apache-2.0" +description = "Hiding Verkle Tree over Pasta Curves" +keywords = ["cryptography", "accumulator", "verkle", "ring-vrf"] +repository = "https://github.com/w3f/ring-proof" + +[dependencies] +w3f-pcs.workspace = true +w3f-plonk-common.workspace = true +w3f-ring-proof = { path = "../w3f-ring-proof" } +ark-pallas = { version = "0.6", default-features = false, features = ["curve"] } +ark-vesta = { version = "0.6", default-features = false } +ark-transcript.workspace = true +ark-std.workspace = true +ark-ff.workspace = true +ark-ec.workspace = true +ark-poly.workspace = true +ark-serialize.workspace = true +rayon = { workspace = true, optional = true } + +[dev-dependencies] +ark-bls12-381.workspace = true +criterion.workspace = true + +[features] +default = ["std"] +std = [ + "ark-std/std", + "ark-ff/std", + "ark-ec/std", + "ark-poly/std", + "ark-serialize/std", + "w3f-pcs/std", + "w3f-plonk-common/std", + "w3f-ring-proof/std", +] +parallel = [ + "std", + "rayon", + "ark-std/parallel", + "ark-ff/parallel", + "ark-ec/parallel", + "ark-poly/parallel", + "w3f-pcs/parallel", + "w3f-plonk-common/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 new file mode 100644 index 0000000..df57c62 --- /dev/null +++ b/pasta-tree/src/auth_path/blinded.rs @@ -0,0 +1,73 @@ +use crate::auth_path::node::LevelWitnessWithBlinding; +use crate::{CycleParams, CycleSide}; +use ark_ec::CurveGroup; +use ark_ff::PrimeField; + +pub struct AuthenticationPathWithBlinding { + pub(crate) c0_path: Vec>, + pub(crate) c1_path: Vec>, +} + +#[derive(Clone, Debug)] +pub struct BlindedAuthenticationPath { + pub(crate) c0_path: Vec, + pub(crate) c1_path: Vec, +} + +impl AuthenticationPathWithBlinding +where + F0: PrimeField, + F1: PrimeField, + C0: CurveGroup, + C1: CurveGroup, +{ + pub(crate) fn apply_bfs( + &self, + params: &CycleParams, + ) -> BlindedAuthenticationPath { + let c0_path = self + .c0_path + .iter() + .map(|c0_level| { + c0_level + .blinded_path_node(¶ms.c0_params.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() + }) + .collect(); + BlindedAuthenticationPath { c0_path, c1_path } + } + pub(crate) fn compute_root( + &self, + params: &CycleParams, + ) -> Result, ()> { + 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)?; + for c1_nodes in self.c1_path.iter() { + let next_c1_path_node = c1_nodes.blinded_path_node(¶ms.c1_params.pcs_params)?; + debug_assert_eq!(parent_on_c1, next_c1_path_node); + (parent_on_c1 == next_c1_path_node).ok_or(())?; + let parent_on_c0 = c1_nodes.compute_parent(¶ms.c0_params)?; + match c0_path_iter.next() { + Some(c0_nodes) => { + let next_c0_path_node = + c0_nodes.blinded_path_node(¶ms.c0_params.pcs_params)?; + debug_assert_eq!(parent_on_c0, next_c0_path_node); + (parent_on_c0 == next_c0_path_node).ok_or(())?; + parent_on_c1 = c0_nodes.compute_parent(¶ms.c1_params)?; + } + None => return Ok(CycleSide::C0(parent_on_c0)), + } + } + Ok(CycleSide::C1(parent_on_c1)) + } +} diff --git a/pasta-tree/src/auth_path/mod.rs b/pasta-tree/src/auth_path/mod.rs new file mode 100644 index 0000000..0124ba3 --- /dev/null +++ b/pasta-tree/src/auth_path/mod.rs @@ -0,0 +1,3 @@ +pub mod blinded; +pub mod node; +pub mod path; diff --git a/pasta-tree/src/auth_path/node.rs b/pasta-tree/src/auth_path/node.rs new file mode 100644 index 0000000..df51a63 --- /dev/null +++ b/pasta-tree/src/auth_path/node.rs @@ -0,0 +1,111 @@ +use crate::CycleSideParams; +use ark_ec::{AffineRepr, CurveGroup}; +use ark_ff::{PrimeField, Zero}; +use ark_std::UniformRand; +use ark_std::rand::Rng; +use w3f_pcs::pcs::ipa::hiding::HidingIpa; + +/// An element of a tree authentication path. A node on the path together with it's sibling. +/// `path_node = self.siblings[self.path_node_idx]` is the node on the path. +/// The next node on the path to the root can be computed as `parent = commit(self.siblings)`. +// TODO: the minimal witness is x-coords of the siblings, and y_i +#[derive(Clone, Debug)] +pub struct LevelWitness { + pub(crate) siblings: Vec, + pub(crate) path_node_idx: usize, +} + +impl LevelWitness { + pub(crate) fn new(siblings: Vec, path_node_idx: usize) -> Result { + debug_assert!(path_node_idx < siblings.len()); + (path_node_idx < siblings.len()).ok_or(())?; + Ok(Self { + siblings, + path_node_idx, + }) + } + + fn x_coords(&self) -> Vec { + self.siblings.iter().map(|p| p.x()).flatten().collect() + } + + pub(crate) fn path_node(&self) -> G { + self.siblings[self.path_node_idx] + } + + pub(crate) fn with_blinding( + &self, + self_bf: G::ScalarField, + parent_bf: G::BaseField, + ) -> LevelWitnessWithBlinding { + LevelWitnessWithBlinding { + level_witness: self.clone(), + bf: self_bf, + parent_bf, + } + } + + pub(crate) fn with_random_blinding( + &self, + parent_bf: G::BaseField, + rng: &mut R, + ) -> LevelWitnessWithBlinding { + self.with_blinding(G::ScalarField::rand(rng), parent_bf) + } + + pub(crate) fn compute_parent>( + &self, + params: &CycleSideParams, + ) -> Result + where + G::BaseField: PrimeField, + { + self.compute_parent_with_bf(params, C::ScalarField::zero()) + } + + fn compute_parent_with_bf>( + &self, + params: &CycleSideParams, + bf: C::ScalarField, + ) -> Result + where + G::BaseField: PrimeField, + { + params.commit_nodes(&self.siblings, bf) + } +} + +/// NB! It is not "blinded", meaning that the blinding factor hasn't been applied. +pub struct LevelWitnessWithBlinding { + pub(crate) level_witness: LevelWitness, + /// the verifier gets `Ci' = siblings[i] + bf.H` + pub(crate) bf: G::ScalarField, + /// Let `Ci = c1.G1 + ... + cm.Gm` -- the non-hiding commitment to a level. + /// Provided that, instead, the verifier gets `Ci' = Ci + bf.H`, + /// when opening the commitment, the prover interprets it as a hiding commitment + /// and needs to know the parent's blinding factor to open to the same values. + /// The root is not blinded, so `parent_bf = 0` for the level below the root. + pub(crate) parent_bf: G::BaseField, // = C::ScalarField +} + +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(), + self.bf, + )?; + Ok(blinded_path_node.0) + } + + pub(crate) fn compute_parent>( + &self, + params: &CycleSideParams, + ) -> Result + where + G::BaseField: PrimeField, + { + 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 new file mode 100644 index 0000000..68a7ab5 --- /dev/null +++ b/pasta-tree/src/auth_path/path.rs @@ -0,0 +1,118 @@ +use crate::auth_path::blinded::AuthenticationPathWithBlinding; +use crate::auth_path::node::LevelWitness; +use crate::{CycleParams, CycleSide}; +use ark_ec::CurveGroup; +use ark_ff::PrimeField; +use ark_std::rand::Rng; + +/// A non-hiding authentication path from a leaf to the root, split between the curves of the cycle. +/// For each tree level (excluding the root), the corresponding element of the authentication path (`LevelWitness`) +/// contains the root-path node at that level, together with all its siblings. +/// That allows to recompute the parent -- the path node at the *previous* level, up to the root. +/// `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. +pub struct AuthenticationPath { + /// Nodes on the `C0` curve. + pub c0_path: Vec>, + /// Nodes on the `C1` curve. + pub c1_path: Vec>, +} + +impl AuthenticationPath +where + F0: PrimeField, + F1: PrimeField, + C0: CurveGroup, + C1: CurveGroup, +{ + pub fn with_blinding(&self, rng: &mut R) -> AuthenticationPathWithBlinding { + 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); + for c1_nodes in self.c1_path.iter() { + let c1_bf = C1::ScalarField::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); + path_1.push(c1_nodes.with_blinding(c1_bf, c0_bf)); + } + None => { + // then the parent of `c1_nodes` is the root + c0_bf = C0::ScalarField::zero(); // `c0_bf = 0` indicates this case + let root_bf = c0_bf; + path_1.push(c1_nodes.with_blinding(c1_bf, root_bf)); + } + } + } + if !c0_bf.is_zero() { + // then `c0_nodes` are the level below the root + let root_bf = C1::ScalarField::zero(); + path_0.push(c0_nodes.with_blinding(c0_bf, root_bf)); + } + + debug_assert_eq!(path_0.len(), self.c0_path.len()); + debug_assert_eq!(path_1.len(), self.c1_path.len()); + + AuthenticationPathWithBlinding { + c0_path: path_0, + c1_path: path_1, + } + } + + fn get_leaf(&self) -> C0::Affine { + self.c0_path[0].path_node() + } + + fn compute_root( + &self, + params: &CycleParams, + ) -> Result, ()> { + 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)?; + for c1_nodes in self.c1_path.iter() { + debug_assert_eq!(parent_on_c1, c1_nodes.path_node()); + (parent_on_c1 == c1_nodes.path_node()).ok_or(())?; + let parent_on_c0 = c1_nodes.compute_parent(¶ms.c0_params)?; + match c0_path_iter.next() { + Some(c0_nodes) => { + debug_assert_eq!(parent_on_c0, c0_nodes.path_node()); + (parent_on_c0 == c0_nodes.path_node()).ok_or(())?; + parent_on_c1 = c0_nodes.compute_parent(¶ms.c1_params)?; + } + None => return Ok(CycleSide::C0(parent_on_c0)), + } + } + Ok(CycleSide::C1(parent_on_c1)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::{random_nodes, random_path}; + use ark_std::{UniformRand, test_rng}; + + #[test] + fn test_auth_path() { + let rng = &mut test_rng(); + + let domain_size = 2usize.pow(9); + let params = + CycleParams::::setup(domain_size, rng); + + let (leaf, path, root) = random_path(¶ms, 2, rng); + + assert_eq!(path.get_leaf(), leaf); + assert_eq!(path.compute_root(¶ms).unwrap(), root); + + let path_with_bfs = path.with_blinding(rng); + assert_eq!(path_with_bfs.compute_root(¶ms).unwrap(), root); + } +} diff --git a/pasta-tree/src/level/mod.rs b/pasta-tree/src/level/mod.rs new file mode 100644 index 0000000..46fd511 --- /dev/null +++ b/pasta-tree/src/level/mod.rs @@ -0,0 +1,93 @@ +mod prover; +mod verifier; + +use crate::Coeffs; +use ark_ec::CurveGroup; +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< + C::ScalarField, + as PCS>::C, + RingCommitments as PCS>::C>, + RingEvaluations, + >, + pcs_opening_proof: AggregateProof>, + todo: Coeffs, +} + +#[cfg(test)] +mod tests { + use crate::CycleParams; + use crate::tests::random_nodes; + use ark_ec::CurveGroup; + use ark_ec::short_weierstrass::{Affine, Projective, SWCurveConfig}; + use ark_ff::PrimeField; + use ark_pallas::PallasConfig; + use ark_std::{UniformRand, end_timer, start_timer, test_rng}; + use ark_vesta::VestaConfig; + + fn _test_level_proof() + where + F0: PrimeField, + F1: PrimeField, + C0: SWCurveConfig, + C1: SWCurveConfig, + { + let rng = &mut test_rng(); + + let domain_size = 2usize.pow(9); + let CycleParams { + c0_params, + c1_params, + } = CycleParams::, Projective>::setup(domain_size, rng); + + let leaf = Affine::::rand(rng); + let (l1_node, mut l2_nodes) = random_nodes(&c1_params, leaf, rng); + let (root, l1_nodes) = random_nodes(&c0_params, l1_node, rng); + + let (_, l1_vk) = c0_params.commit_children(l1_nodes.siblings.as_slice(), F0::zero()); + let root_fc = l1_vk.fixed_columns_committed; + + let capacity = c0_params.piop_params.keyset_part_size; + let l1_nodes_with_bf = l1_nodes.with_random_blinding(F0::zero(), rng); + let t_prove = start_timer!(|| format!( + "Proving 1st level of a curve tree, domain_size={domain_size}, capacity={capacity}" + )); + let (l1_node_blinded, l1_proof) = c0_params.prove_level(&l1_nodes_with_bf, rng); + end_timer!(t_prove); + + let t_verify = start_timer!(|| format!("Verifying a single-level proof")); + assert!(c0_params.verify_level(root_fc, l1_node_blinded, l1_proof)); + end_timer!(t_verify); + + let capacity = c1_params.piop_params.keyset_part_size; + + let (_, l2_vk) = + c1_params.commit_children(l2_nodes.siblings.as_slice(), l1_nodes_with_bf.bf); + let l1_node_fc = l2_vk.fixed_columns_committed; + assert_eq!(l1_node_fc.points[0].0, l1_node_blinded); + + let l2_nodes_with_bf = l2_nodes.with_random_blinding(l1_nodes_with_bf.bf, rng); + let t_prove = start_timer!(|| format!( + "Proving 2nd level of a curve tree, domain_size={domain_size}, capacity={capacity}" + )); + let (blinded_leaf, l2_proof) = c1_params.prove_level(&l2_nodes_with_bf, rng); + end_timer!(t_prove); + + let t_verify = start_timer!(|| format!("Verifying a single-level proof")); + assert!(c1_params.verify_level(l1_node_fc, blinded_leaf, l2_proof)); + end_timer!(t_verify); + } + + // cargo test test_level_proof --release --features="print-trace" -- --show-output + // cargo test test_level_proof --release --features="print-trace parallel" -- --show-output + #[test] + fn test_level_proof() { + _test_level_proof::<_, _, PallasConfig, VestaConfig>() + } +} diff --git a/pasta-tree/src/level/prover.rs b/pasta-tree/src/level/prover.rs new file mode 100644 index 0000000..05c1b13 --- /dev/null +++ b/pasta-tree/src/level/prover.rs @@ -0,0 +1,88 @@ +use crate::auth_path::node::LevelWitnessWithBlinding; +use crate::level::LevelProof; +use crate::{Coeffs, CycleSideParams, IPACommitment}; +use ark_ec::CurveGroup; +use ark_ec::short_weierstrass::{Affine, SWCurveConfig}; +use ark_ff::Zero; +use ark_poly::Polynomial; +use ark_std::rand::Rng; +use ark_std::{UniformRand, end_timer, start_timer}; +use std::collections::BTreeSet; +use w3f_pcs::pcs::PcsParams; +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> +{ + pub fn prove_level( + &self, + witness: &LevelWitnessWithBlinding>, + rng: &mut R, + ) -> (Affine, LevelProof) { + let (fixed_columns, verifier_key) = + self.commit_children(&witness.level_witness.siblings, witness.parent_bf); + let piop = PiopProver::build( + &self.piop_params, + fixed_columns, + witness.level_witness.path_node_idx, + witness.bf, + ); + let blinded_node = > as ProverPiop< + C::ScalarField, + IPACommitment, + >>::result(&piop); + // let blinded_parent = verifier_key.fixed_columns_committed.points[0].clone(); + let plonk_prover = PlonkProver::, _>::init( + self.pcs_params.ck(), + verifier_key, + ArkTranscript::new(b"pasta-tree-level-proof"), + ); + let (pcs_openings, piop_proof, mut transcript) = plonk_prover.reduce_to_pcs_opening(piop); + let PcsOpeningAt2Points { + polys_at_zeta, + polys_at_zeta_omega, + zeta, + zeta_omega, + } = pcs_openings; + + let mut coord_vecs = vec![vec![zeta]; polys_at_zeta.len()]; + coord_vecs.push(vec![zeta_omega]); + let polys = [polys_at_zeta, polys_at_zeta_omega].concat(); + let mut bfs = vec![C::ScalarField::zero(); polys.len()]; + bfs[0] = witness.parent_bf; + + let coord_sets: Vec> = coord_vecs + .iter() + .cloned() + .map(BTreeSet::from_iter) + .collect(); + + let todo = Coeffs(C::ScalarField::rand(rng), C::ScalarField::rand(rng)); + let t_open = start_timer!(|| format!( + "Opening IPA ring-proof with shplonk, {} polys, max_degree = {}", + polys.len(), + polys[polys.len() - 2].degree() // the quotient + )); + let pcs_opening_proof = Shplonk::>::open_many_hiding( + &self.pcs_params, + &polys, + &bfs, + &coord_sets, + &mut todo.clone(), + rng, + ); + end_timer!(t_open); + + let proof = LevelProof { + piop_proof, + pcs_opening_proof, + todo, + }; + (blinded_node, proof) + } +} diff --git a/pasta-tree/src/level/verifier.rs b/pasta-tree/src/level/verifier.rs new file mode 100644 index 0000000..e46104c --- /dev/null +++ b/pasta-tree/src/level/verifier.rs @@ -0,0 +1,85 @@ +use crate::level::LevelProof; +use crate::{CycleSideParams, IPACommitment}; +use ark_ec::CurveGroup; +use ark_ec::short_weierstrass::{Affine, SWCurveConfig}; +use ark_std::{end_timer, start_timer}; +use w3f_pcs::pcs::PcsParams; +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> +{ + pub fn verify_level( + &self, + parent: FixedColumnsCommitted>, + blinded_child: Affine, + level_proof: LevelProof, + ) -> bool { + let verifier_key: VerifierKey> = VerifierKey { + pcs_raw_vk: self.pcs_params.raw_vk(), + fixed_columns_committed: parent.clone(), + }; + let plonk_verifier: PlonkVerifier, _> = PlonkVerifier::init( + self.pcs_params.vk(), + &verifier_key, + ArkTranscript::new(b"pasta-tree-level-proof"), + ); + + let LevelProof { + piop_proof, + pcs_opening_proof, + mut todo, + } = level_proof; + + let (challenges, mut rng) = plonk_verifier.restore_challenges( + &blinded_child, + &piop_proof, + // '1' accounts for the quotient polynomial that is aggregated together with the columns + PiopVerifier::, Affine>::N_COLUMNS + 1, + PiopVerifier::, Affine>::N_CONSTRAINTS, + ); + let seed = self.piop_params.seed; + let seed_plus_result = (seed + blinded_child).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 PcsOpeningAt2Points { + open_at_zeta, + open_at_zeta_omega, + zeta, + zeta_omega, + vals_at_zeta, + vals_at_zeta_omega, + } = plonk_verifier.evaluate_piop(piop, piop_proof, challenges); + + let mut coord_vecs = vec![vec![zeta]; open_at_zeta.len()]; + coord_vecs.push(vec![zeta_omega]); + let polys = [open_at_zeta, open_at_zeta_omega].concat(); + let mut vals: Vec<_> = vals_at_zeta.into_iter().map(|v| vec![v]).collect(); + vals.push(vals_at_zeta_omega); + let t_verify = start_timer!(|| "Verifying IPA shplonk opening"); + let valid = Shplonk::>::verify_many( + &self.pcs_params.vk(), + &polys, + pcs_opening_proof, + &coord_vecs, + &vals, + &mut todo, + ); + end_timer!(t_verify); + + valid + } +} diff --git a/pasta-tree/src/lib.rs b/pasta-tree/src/lib.rs new file mode 100644 index 0000000..f123c25 --- /dev/null +++ b/pasta-tree/src/lib.rs @@ -0,0 +1,806 @@ +#![feature(bool_to_result)] + +use ark_ec::{AffineRepr, CurveGroup, PrimeGroup}; +use ark_ff::{PrimeField, Zero}; +use ark_std::rand::Rng; +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}; + +pub mod auth_path; +pub mod level; +pub mod prover; +pub mod verifier; + +type IPACommitment = as PCS<::ScalarField>>::C; + +struct CycleSideParams> { + pcs_params: HidingIpa, + piop_params: PiopParams, +} + +struct CycleParams< + C0: CurveGroup, + C1: CurveGroup, +> { + c0_params: CycleSideParams, + c1_params: CycleSideParams, +} + +#[derive(Clone)] +pub struct CycleSideProof> { + piop_proofs: Vec< + PiopProof, RingCommitments>, RingEvaluations>, + >, + pcs_proof: AggregateProof>, + todo: Coeffs, + fixed_columns_committed: Vec>>, +} + +#[derive(Clone)] +pub struct CurveTreeProof< + F0: PrimeField, + F1: PrimeField, + C0: CurveGroup, + C1: CurveGroup, +> { + c0_proof: CycleSideProof, + c1_proof: CycleSideProof, +} + +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, + }, + } + } +} + +fn piop_params, R: Rng>( + domain_size: usize, + h: G, + rng: &mut R, +) -> PiopParams { + let domain = Domain::::new(domain_size, true); + let seed = G::rand(rng); + let padding = G::rand(rng); + PiopParams::setup(domain, h, seed, padding) +} + +impl> CycleSideParams { + pub fn commit_children( + &self, + children: &[G], + 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) + } + + 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 struct CycleSideParams { +// ipa_pcs: HidingIpa, +// domain: Radix2EvaluationDomain, +// extra_elements: Vec, +// extra_commitment: C, +// } + +// pub struct CycleParams { +// c0_params: CycleSideParams, +// c1_params: CycleSideParams, +// } + +// impl CycleSideParams { +// fn setup(log_n: u32, rng: &mut R) -> Result { +// let n = 2usize.pow(log_n); +// let domain = Radix2EvaluationDomain::new(n).ok_or(())?; +// let ipa_pcs = HidingIpa::setup(n - 1, rng); +// Ok(Self { +// ipa_pcs, +// domain, +// extra_elements: vec![], +// extra_commitment: C::zero(), +// }) +// } +// +// fn commit_node( +// &self, +// children_x_coords: Vec, +// blinding: C::ScalarField, +// ) -> Result { +// (children_x_coords.len() <= self.domain.size() - self.extra_elements.len()).ok_or(())?; +// let evals = Evaluations::from_vec_and_domain(children_x_coords, self.domain); +// let poly = evals.interpolate_by_ref(); +// let x_coords_committed = self.ipa_pcs.commit_hiding(&poly, blinding)?.0; +// let node_committed = x_coords_committed + self.extra_commitment; +// Ok(node_committed) +// } +// } + +// impl CycleParams { +// fn setup(log_n: u32, rng: &mut R) -> Result { +// let c0_params = CycleSideParams::setup(log_n, rng)?; +// let c1_params = CycleSideParams::setup(log_n, rng)?; +// Ok(Self { +// c0_params, +// c1_params, +// }) +// } +// } + +#[derive(Debug, PartialEq)] +enum CycleSide { + C0(C0), + C1(C1), +} + +#[derive(Clone)] +pub struct Coeffs(F, F); +impl> ShplonkTranscript for Coeffs { + fn get_gamma(&mut self) -> F { + self.0 + } + + fn commit_to_q(&mut self, _q: &CS::C) {} + + fn get_zeta(&mut self) -> F { + self.1 + } +} + +#[cfg(test)] +mod tests { + use super::*; + 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::{AffineRepr, CurveGroup}; + use ark_ff::PrimeField; + use ark_ff::{BigInteger, Field, Zero}; + use ark_pallas::PallasConfig; + use ark_poly::DenseUVPolynomial; + use ark_poly::Polynomial; + use ark_std::iterable::Iterable; + use ark_std::rand::Rng; + use ark_std::{UniformRand, cfg_iter_mut, end_timer, start_timer, test_rng}; + use ark_vesta::VestaConfig; + use std::collections::BTreeSet; + use w3f_pcs::Poly; + use w3f_pcs::pcs::PcsParams; + use w3f_pcs::pcs::commitment::WrappedAffine; + use w3f_pcs::pcs::ipa::IPA; + use w3f_pcs::pcs::{PCS, RawVerifierKey}; + use w3f_pcs::shplonk::Shplonk; + use w3f_plonk_common::piop::ProverPiop; + use w3f_plonk_common::prover::{PcsOpeningAt2Points, PlonkProver}; + use w3f_plonk_common::test_helpers::random_vec; + use w3f_ring_proof::piop::prover::PiopProver; + use w3f_ring_proof::{ArkTranscript, PiopParams, index}; + + use crate::auth_path::node::LevelWitness; + use crate::auth_path::path::AuthenticationPath; + #[cfg(feature = "parallel")] + use rayon::prelude::*; + use w3f_plonk_common::domain::Domain; + + type PallasIPA = IPA; + type PallasC = WrappedAffine; + + fn random_witness, R: Rng>( + params: &CycleSideParams, + 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; + LevelWitness { + siblings: nodes, + path_node_idx: i, + } + } + + pub fn random_nodes, R: Rng>( + params: &CycleSideParams, + path_node: G, + rng: &mut R, + ) -> (C::Affine, LevelWitness) { + let level_witness = random_witness(params, path_node, rng); + let parent = level_witness.compute_parent(params).unwrap(); + (parent, level_witness) + } + + pub fn random_path< + C0: CurveGroup, + C1: CurveGroup, + R: Rng, + >( + params: &CycleParams, + length: usize, + rng: &mut R, + ) -> ( + C0::Affine, + AuthenticationPath, + CycleSide, + ) { + let c0_len = (length + 1) / 2; + let c1_len = length / 2; + debug_assert_eq!(c0_len + c1_len, length); + 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 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); + let (parent_on_c0, c1_nodes) = random_nodes(¶ms.c0_params, parent_on_c1, rng); + c0_path_node = parent_on_c0; + c0_path.push(c0_nodes); + c1_path.push(c1_nodes); + } + + let root = if c0_len > c1_len { + let (root_on_c1, c0_nodes) = random_nodes(¶ms.c1_params, c0_path_node, rng); + c0_path.push(c0_nodes); + CycleSide::C1(root_on_c1) + } else { + CycleSide::C0(c0_path_node) + }; + + let path = AuthenticationPath { c0_path, c1_path }; + (leaf, path, root) + } + + fn setup, G: AffineRepr>( + rng: &mut R, + domain_size: usize, + ) -> (CS::Params, PiopParams) { + let setup_degree = 3 * domain_size; + let pcs_params = CS::setup(setup_degree, rng); + let domain = Domain::new(domain_size, true); + let piop_params = PiopParams::setup(domain, G::rand(rng), G::rand(rng), G::rand(rng)); + (pcs_params, piop_params) + } + + fn _test_proof() + where + F0: PrimeField, + F1: PrimeField, + C0: SWCurveConfig, + C1: SWCurveConfig, + { + let rng = &mut test_rng(); + + let domain_size = 2usize.pow(9); + let params = CycleParams::, Projective>::setup(domain_size, rng); + let (leaf, path, wrapped_root) = random_path(¶ms, 4, rng); + let root = match wrapped_root { + CycleSide::C0(root) => root, + _ => panic!(), + }; + + let path_with_bf = path.with_blinding(rng); + // let l1_nodes = &path_with_bf.c1_path[1]; + // let l2_nodes = &path_with_bf.c0_path[1]; + // let l3_nodes = &path_with_bf.c1_path[0]; + // let l4_nodes = &path_with_bf.c0_path[0]; + // + // let (_, l1_vk) = params.c0_params.commit_children(l1_nodes.level_witness.siblings.as_slice(), l1_nodes.parent_bf); + // let root_fc = l1_vk.fixed_columns_committed; + // assert_eq!(root_fc.points[0].0, root); + // + // let (l1_node_blinded, l1_proof) = params.c0_params.prove_level(l1_nodes, rng); + // assert!(params.c0_params.verify_level(root_fc, l1_node_blinded, l1_proof)); + // + // let (_, l2_vk) = params.c1_params.commit_children(l2_nodes.level_witness.siblings.as_slice(), l2_nodes.parent_bf); + // let l2_node_fc = l2_vk.fixed_columns_committed; + // assert_eq!(l2_node_fc.points[0].0, l1_node_blinded); + // + // let (l2_node_blinded, l2_proof) = params.c1_params.prove_level(l2_nodes, rng); + // assert!(params.c1_params.verify_level(l2_node_fc, l2_node_blinded, l2_proof)); + // + // let (_, l3_vk) = params.c0_params.commit_children(l3_nodes.level_witness.siblings.as_slice(), l3_nodes.parent_bf); + // let l3_node_fc = l3_vk.fixed_columns_committed; + // assert_eq!(l3_node_fc.points[0].0, l2_node_blinded); + // + // let (l3_node_blinded, l3_proof) = params.c0_params.prove_level(l3_nodes, rng); + // assert!(params.c0_params.verify_level(l3_node_fc, l3_node_blinded, l3_proof)); + // + // let (_, l4_vk) = params.c1_params.commit_children(l4_nodes.level_witness.siblings.as_slice(), l4_nodes.parent_bf); + // let l4_node_fc = l4_vk.fixed_columns_committed; + // assert_eq!(l4_node_fc.points[0].0, l3_node_blinded); + // + // let (l4_node_blinded, l4_proof) = params.c1_params.prove_level(l4_nodes, rng); + // assert!(params.c1_params.verify_level(l4_node_fc, l4_node_blinded, l4_proof)); + let t_prove = start_timer!(|| "Proving CurveTree membership, H=4, M=512, C=252"); + let (auth_path, proof) = params.prove(path, rng); + end_timer!(t_prove); + + // assert_eq!(auth_path.c0_path, vec![l4_node_blinded, l2_node_blinded]); + // assert_eq!(auth_path.c1_path, vec![l3_node_blinded, l1_node_blinded]); + // + // assert!(params.c0_params.verify_side(auth_path.c1_path, proof.c0_proof.clone())); + // + // assert_eq!(proof.c1_proof.fixed_columns_committed.len(), 2); + // assert_eq!(proof.c1_proof.fixed_columns_committed[0].points[0].0, l3_node_blinded); + // assert_eq!(proof.c1_proof.fixed_columns_committed[1].points[0].0, l1_node_blinded); + // + // assert!(params.c1_params.verify_side(auth_path.c0_path, proof.c1_proof.clone())); + + 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>(); + } + + // // cargo test test_pasta_ring_plonk --release --features="print-trace" -- --show-output + // #[test] + // fn test_pasta_ring_plonk() { + // let rng = &mut test_rng(); + // + // // setup + // let domain_size = 2usize.pow(9); + // let (pcs_params, piop_params) = + // setup::<_, PallasIPA, ark_vesta::Affine>(rng, domain_size); + // let keyset_size = piop_params.keyset_part_size; + // let pks = random_vec::(keyset_size, rng); + // let (prover_key, verifier_key) = index::<_, PallasIPA, _>(&pcs_params, &piop_params, &pks); + // let blinding = ark_vesta::Fr::rand(rng); + // let pk_idx = rng.gen_range(0..keyset_size); + // let blinded_pk = piop_params.blind_pk(pks[pk_idx], blinding); + // + // // prover + // let fs = ArkTranscript::new(b"pasta-ring-proof-test"); + // let prover = RingProver::init(prover_key, piop_params.clone(), 0, fs.clone()); + // let t_prove = start_timer!(|| format!( + // "Proving IPA ring-proof with plonk, domain_size={domain_size}, keyset_size={keyset_size}" + // )); + // let (blinded_pk_, proof) = prover.rerandomize_pk(pk_idx, blinding); + // end_timer!(t_prove); + // assert_eq!(blinded_pk_, blinded_pk); + // + // // verifier + // let ring_verifier = RingVerifier::init(verifier_key, piop_params, fs); + // let t_verify = start_timer!(|| "Verifying IPA plonk opening"); + // let valid = ring_verifier.verify(proof, blinded_pk); + // end_timer!(t_verify); + // assert!(valid); + // } + + // cargo test test_pasta_ring_shplonk --release --features="print-trace" -- --show-output + #[test] + fn test_pasta_ring_shplonk() { + let rng = &mut test_rng(); + + // setup + let domain_size = 2usize.pow(9); + let (pcs_params, piop_params) = setup::<_, PallasIPA, ark_vesta::Affine>(rng, domain_size); + let keyset_size = piop_params.keyset_part_size; + let pks = random_vec::(keyset_size, rng); + let (prover_key, verifier_key) = index::<_, PallasIPA, _>(&pcs_params, &piop_params, &pks); + let blinding = ark_vesta::Fr::rand(rng); + let pk_idx = rng.gen_range(0..keyset_size); + // let blinded_pk = { + // let prover_pk = pks[pk_idx].clone(); + // let blinded_pk = prover_pk + piop_params.h * blinding; + // blinded_pk.into_affine() + // }; + let pcs_ck = prover_key.pcs_ck; + let pcs_vk = verifier_key.pcs_raw_vk.prepare(); + + // prover + let piop = PiopProver::::build( + &piop_params, + prover_key.fixed_columns.clone(), + pk_idx, + blinding, + ); + let t_prove = start_timer!(|| format!( + "Proving IPA ring-proof with shplonk, domain_size={domain_size}, keyset_size={keyset_size}" + )); + let zeta = ark_pallas::Fr::rand(rng); + let columns = as ProverPiop>::columns(&piop); + let (quotient, agg_lin) = { + let constraints = + as ProverPiop>::constraints(&piop); + let alphas: Vec<_> = (0..constraints.len()) + .map(|_| ark_pallas::Fr::rand(rng)) + .collect(); + let agg_constraint_poly = + PlonkProver::::aggregate_evaluations( + &constraints, + &alphas, + ) + .interpolate(); + let quotient = piop_params + .domain + .divide_by_vanishing_poly(&agg_constraint_poly); + let constraints_lin = + as ProverPiop>::constraints_lin( + &piop, &zeta, + ); + let agg_lin = w3f_pcs::aggregation::single::aggregate_polys(&constraints_lin, &alphas); + (quotient, agg_lin) + }; + + let mut polys = columns; + polys.push(quotient); + let mut coord_vecs = vec![vec![zeta]; polys.len()]; + polys.push(agg_lin.clone()); + coord_vecs.push(vec![zeta * piop_params.domain.omega()]); + + // commitments + let mut poly_cs = verifier_key.fixed_columns_committed.as_vec(); + let t_commit = start_timer!(|| format!( + "Commiting to {} columns of degree = {} and the quotient of degree = {}", + polys.len() - 5, + polys[3].degree(), + polys[7].degree() + )); + // skip the instance columns and the linearirization polynomial `agg_lin` + poly_cs.extend( + polys[3..polys.len() - 1] + .iter() + .map(|p| PallasIPA::commit(&pcs_ck, p).unwrap()), + ); + end_timer!(t_commit); + poly_cs.push(PallasIPA::commit(&pcs_ck, &agg_lin).unwrap()); + + let coord_sets: Vec> = coord_vecs + .iter() + .cloned() + .map(BTreeSet::from_iter) + .collect(); + let vals: Vec<_> = polys + .iter() + .zip(coord_vecs.iter()) + .map(|(f, xs)| xs.iter().map(|x| f.evaluate(&x)).collect::>()) + .collect(); + + let transcript = &mut Coeffs(ark_pallas::Fr::rand(rng), ark_pallas::Fr::rand(rng)); + let t_open = start_timer!(|| format!( + "Opening IPA ring-proof with shplonk, {} polys, max_degree = {}", + polys.len(), + polys[7].degree() + )); + let proof = Shplonk::::open_many( + &pcs_ck, + &polys, + &coord_sets, + // ark_pallas::Fr::zero(), + transcript, + ); + end_timer!(t_open); + end_timer!(t_prove); + + // verifier + let t_verify = start_timer!(|| "Verifying IPA shplonk opening"); + let valid = Shplonk::::verify_many( + &pcs_vk, + &poly_cs, + proof, + &coord_vecs, + &vals, + transcript, + ); + end_timer!(t_verify); + assert!(valid); + } + + fn _bench_msm(log_n: u32) { + let rng = &mut test_rng(); + let n = 2usize.pow(log_n); + let (scalars, bases): (Vec<_>, Vec<_>) = (0..n) + .map(|_| (C::ScalarField::rand(rng), C::Affine::rand(rng))) + .unzip(); + let t_msm = start_timer!(|| format!( + "log(n)={log_n}, MSM on {}", + ark_std::any::type_name::() + )); + let _res = C::msm(&bases, &scalars); + end_timer!(t_msm); + } + + // cargo test bench_msms --release --features="print-trace" -- --show-output + // qcargo test bench_msms --release --features="parallel print-trace" -- --show-output + #[test] + fn bench_msms() { + let log_n = 9; + + _bench_msm::(log_n); + _bench_msm::(log_n + 1); + // _bench_msm::(log_n); + // _bench_msm::(log_n); + // _bench_folding::(log_n); + // _bench_folding::(log_n + 1); + _bench_folding(log_n); + _bench_folding(log_n + 1); + + let rng = &mut test_rng(); + let n = 2usize.pow(log_n); + + let n3 = 3 * n; + let pcs_params = PallasIPA::setup(n3, rng); + + let p = Poly::::rand(n, rng); + let t_ipa_commit = start_timer!(|| format!("IPA commitment to a degree {n} polynomial")); + let _c = PallasIPA::commit(&pcs_params.ck(), &p); + end_timer!(t_ipa_commit); + + let p = Poly::::rand(n3, rng); + let t_ipa_commit = start_timer!(|| format!("IPA commitment to a degree 3*{n} polynomial")); + let _c = PallasIPA::commit(&pcs_params.ck(), &p); + end_timer!(t_ipa_commit); + } + + fn mul_endo_wnaf( + p: ark_pallas::Projective, + k1: (bool, ark_pallas::Fr), + k2: (bool, ark_pallas::Fr), + ) -> ark_pallas::Projective { + let mut p1 = p; + let mut p2 = PallasConfig::endomorphism(&p); + if !k1.0 { + p1 = -p1; + } + if !k2.0 { + p2 = -p2; + } + let w_size = 4; + let wnaf = WnafContext::new(w_size); + let p1_table = wnaf.table(p1); + let p2_table = wnaf.table(p2); + let k1_wnaf = k1.1.into_bigint().find_wnaf(w_size).unwrap(); + let mut k2_wnaf = k2.1.into_bigint().find_wnaf(w_size).unwrap(); + k2_wnaf.resize(k1_wnaf.len(), 0); + + let mut result = ark_pallas::Projective::zero(); + let mut found_non_zero = false; + for (n1, n2) in k1_wnaf.into_iter().zip(k2_wnaf).rev() { + if found_non_zero { + result.double_in_place(); + } + + if n1 != 0 || n2 != 0 { + found_non_zero = true; + if n1 > 0 { + result += &p1_table[(n1 / 2) as usize]; + } + if n1 < 0 { + result -= &p1_table[((-n1) / 2) as usize]; + } + if n2 > 0 { + result += &p2_table[(n2 / 2) as usize]; + } + if n2 < 0 { + result -= &p2_table[((-n2) / 2) as usize]; + } + } + } + result + } + + fn _bench_folding(log_n: u32) { + let rng = &mut test_rng(); + let n = 2usize.pow(log_n); + let (l, r): (Vec, Vec) = (0..n) + .map(|_| (ark_pallas::Affine::rand(rng), ark_pallas::Affine::rand(rng))) + .unzip(); + let x = ark_pallas::Fr::rand(rng); + let _timer = start_timer!(|| format!("Naive folding, log(n) = {log_n}")); + let res: Vec = ark_std::cfg_iter!(l) + .zip(r.clone()) + .map(|(l, r)| r * x + l) + .collect(); + end_timer!(_timer); + + let _timer = start_timer!(|| format!("Naive folding with endo, log(n) = {log_n}")); + let res_: Vec = ark_std::cfg_into_iter!(l.clone()) + .zip(ark_std::cfg_into_iter!(r.clone())) + .map(|(l, r)| l + ::glv_mul_affine(r, x)) + .collect(); + end_timer!(_timer); + assert_eq!(res_, res); + + let _timer = + start_timer!(|| format!("Naive folding with endo and w-NAF, log(n) = {log_n}")); + let ((sgn_k1, k1), (sgn_k2, k2)) = PallasConfig::scalar_decomposition(x); + let res_: Vec = ark_std::cfg_into_iter!(l) + .zip(ark_std::cfg_iter!(r)) + .map(|(l, r)| l + mul_endo_wnaf(r.into_group(), (sgn_k1, k1), (sgn_k2, k2))) + .collect(); + end_timer!(_timer); + assert_eq!(res_, res); + } + + fn batch_double_affine(bases: Vec>) -> Vec> { + let mut denoms: Vec = ark_std::cfg_iter!(bases).map(|p| p.y + p.y).collect(); + + ark_ff::batch_inversion(&mut denoms); + + ark_std::cfg_iter!(bases) + .zip(denoms) + .map(|(p, _2y_inv)| { + let (x, y) = p.xy().unwrap(); + let t = + _2y_inv * (x.square() * C::BaseField::from(3) + ::COEFF_A); // (3x^2 + a) / 2y + let x_n = t.square() - x - x; + let y_n = t * (x - x_n) - y; + Affine::::new_unchecked(x_n, y_n) + }) + .collect() + } + + fn batch_double_affine_in_place(bases: &mut [Affine]) { + let three = C::BaseField::from(3); + let sw_a = ::COEFF_A; + let mut denoms: Vec = ark_std::cfg_iter!(bases).map(|p| p.y + p.y).collect(); + + ark_ff::batch_inversion(&mut denoms); + // ark_ff::batch_inversion_and_mul(&mut denoms, &C::BaseField::one()); + + cfg_iter_mut!(bases) + .zip(ark_std::cfg_into_iter!(denoms)) + .for_each(|(p, _2y_inv)| { + let t = _2y_inv * (p.x.square() * three + sw_a); // (3x^2 + a) / 2y + let old_x = p.x; + p.x = t.square() - p.x - p.x; + p.y = t * (old_x - p.x) - p.y; + }) + } + + fn batch_add_affine( + bases1: Vec>, + bases2: Vec>, + ) -> Vec> { + let mut denoms: Vec = ark_std::cfg_iter!(bases1) + .zip(ark_std::cfg_iter!(bases2)) + .map(|(p1, p2)| p2.x - p1.x) + .collect(); + + ark_ff::batch_inversion(&mut denoms); + + ark_std::cfg_iter!(bases1) + .zip(ark_std::cfg_iter!(bases2)) + .zip(ark_std::cfg_iter!(denoms)) + .map(|((p1, p2), _x2_m_x1)| { + let (x1, y1) = p1.xy().unwrap(); + let (x2, y2) = p2.xy().unwrap(); + let t = (y2 - y1) * _x2_m_x1; + let x_n = t.square() - x1 - x2; + let y_n = t * (x1 - x_n) - y1; + Affine::::new_unchecked(x_n, y_n) + }) + .collect() + } + + fn batch_mul_by_x_affine( + bases: Vec>, + x: C::ScalarField, + ) -> Vec> { + let mut res: Vec> = bases.clone(); + for b in ark_ff::BitIteratorBE::without_leading_zeros(x.into_bigint()).skip(1) { + batch_double_affine_in_place(&mut res); + if b { + res = batch_add_affine(res, bases.clone()); + } + } + res + } + + #[test] + fn bench_folding() { + let rng = &mut test_rng(); + let log_n = 10; + _bench_folding(log_n); + + let n = 2usize.pow(log_n); + let bases: Vec<_> = (0..n).map(|_| ark_pallas::Affine::rand(rng)).collect(); + let x = ark_pallas::Fr::rand(rng); + let dbl: Vec<_> = bases + .iter() + .map(|p| { + let mut p = p.into_group(); + p.double_in_place(); + p.into_affine() + }) + .collect(); + assert_eq!(dbl, batch_double_affine(bases.clone())); + + let bases2: Vec<_> = (0..n).map(|_| ark_pallas::Affine::rand(rng)).collect(); + let bases1 = bases.clone(); + let add: Vec<_> = bases + .into_iter() + .zip(bases2.iter()) + .map(|(p1, p2)| p1 + p2) + .collect(); + assert_eq!(add, batch_add_affine(bases1.clone(), bases2.clone())); + + let _timer = start_timer!(|| format!("Batch affine folding, log(n) = {log_n}")); + let x_bases2 = batch_mul_by_x_affine(bases2.clone(), x); + let res = batch_add_affine(bases1.clone(), x_bases2); + end_timer!(_timer); + + let _timer = start_timer!(|| format!("Naive folding, log(n) = {log_n}")); + let res_: Vec = ark_std::cfg_into_iter!(bases1) + .zip(ark_std::cfg_into_iter!(bases2)) + .map(|(l, r)| l + r * x) + .collect(); + let _to_affine = start_timer!(|| "batch affine conversion"); + let res_ = ark_pallas::Projective::normalize_batch(&res_); + end_timer!(_to_affine); + end_timer!(_timer); + + assert_eq!(res_, res); + } +} diff --git a/pasta-tree/src/prover.rs b/pasta-tree/src/prover.rs new file mode 100644 index 0000000..6205468 --- /dev/null +++ b/pasta-tree/src/prover.rs @@ -0,0 +1,124 @@ +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 ark_ec::CurveGroup; +use ark_ec::short_weierstrass::{Affine, Projective, SWCurveConfig}; +use ark_ff::{PrimeField, Zero}; +use ark_std::UniformRand; +use ark_std::rand::Rng; +use std::collections::BTreeSet; +use w3f_pcs::pcs::PcsParams; +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 CycleParams, Projective> +where + F0: PrimeField, + F1: PrimeField, + C0: SWCurveConfig, + C1: SWCurveConfig, +{ + pub fn prove( + &self, + auth_path: AuthenticationPath, Projective>, + rng: &mut R, + ) -> ( + BlindedAuthenticationPath, Projective>, + CurveTreeProof, Projective>, + ) { + let auth_path_with_bf = auth_path.with_blinding(rng); + let blinded_auth_path = auth_path_with_bf.apply_bfs(&self); + 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); + let c1_proof = + self.c1_params + .prove_side(blinded_auth_path.c0_path, &auth_path_with_bf.c0_path, rng); + (auth_path, CurveTreeProof { c0_proof, c1_proof }) + } +} + +impl> + CycleSideParams> +{ + pub fn prove_side( + &self, + blinded_path: Vec>, + witness: &[LevelWitnessWithBlinding>], + 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); + + 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 (pcs_openings, piop_proof, mut transcript) = + plonk_prover.reduce_to_pcs_opening(piop); + piop_proofs.push(piop_proof); + let PcsOpeningAt2Points { + polys_at_zeta, + polys_at_zeta_omega, + zeta, + zeta_omega, + } = pcs_openings; + // println!("zeta = {zeta}, q(zeta) = {}", polys_at_zeta[polys_at_zeta.len() - 1].evaluate(&zeta)); + coords.extend(vec![BTreeSet::from([zeta]); polys_at_zeta.len()]); + polys.extend(polys_at_zeta); + coords.extend(vec![ + BTreeSet::from([zeta_omega]); + polys_at_zeta_omega.len() + ]); + polys.extend(polys_at_zeta_omega); + bfs.push(level.parent_bf); + bfs.resize(polys.len(), C::ScalarField::zero()); + } + + let todo = Coeffs(C::ScalarField::rand(rng), C::ScalarField::rand(rng)); + let pcs_proof = Shplonk::>::open_many_hiding( + &self.pcs_params, + &polys, + &bfs, + &coords, + &mut todo.clone(), + rng, + ); + + let proof = CycleSideProof { + piop_proofs, + pcs_proof, + todo, + fixed_columns_committed, + }; + proof + } +} diff --git a/pasta-tree/src/verifier.rs b/pasta-tree/src/verifier.rs new file mode 100644 index 0000000..5ddbb65 --- /dev/null +++ b/pasta-tree/src/verifier.rs @@ -0,0 +1,134 @@ +use crate::auth_path::blinded::BlindedAuthenticationPath; +use crate::{CurveTreeProof, CycleParams, CycleSide, CycleSideParams, CycleSideProof}; +use ark_ec::CurveGroup; +use ark_ec::short_weierstrass::{Affine, Projective, SWCurveConfig}; +use ark_ff::PrimeField; +use w3f_pcs::pcs::PcsParams; +use w3f_pcs::pcs::ipa::hiding::HidingIpa; +use w3f_pcs::shplonk::Shplonk; +use w3f_plonk_common::verifier::{PcsOpeningAt2Points, PlonkVerifier}; +use w3f_ring_proof::ArkTranscript; +use w3f_ring_proof::piop::verifier::PiopVerifier; + +impl CycleParams, Projective> +where + F0: PrimeField, + F1: PrimeField, + C0: SWCurveConfig, + C1: SWCurveConfig, +{ + pub fn verify( + &self, + auth_path: BlindedAuthenticationPath, Projective>, + proof: CurveTreeProof, Projective>, + root: CycleSide, Affine>, + ) -> 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 c0_proof = self + .c0_params + .verify_side(auth_path.c1_path, proof.c0_proof); + assert!(c0_proof); + let c1_proof = self + .c1_params + .verify_side(auth_path.c0_path, proof.c1_proof); + assert!(c1_proof); + c0_proof && c1_proof + } +} + +impl> + CycleSideParams> +{ + pub fn verify_side( + &self, + blinded_path: Vec>, + side_proof: CycleSideProof, + ) -> bool { + let plonk_verifier: PlonkVerifier, _> = PlonkVerifier::init( + self.pcs_params.vk(), + &blinded_path, + 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); + + for ((blinded_node, piop_proof), parent) in blinded_path + .iter() + .zip(side_proof.piop_proofs.into_iter()) + .zip(side_proof.fixed_columns_committed.into_iter()) + { + let (challenges, mut 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 PcsOpeningAt2Points { + open_at_zeta, + open_at_zeta_omega, + zeta, + zeta_omega, + vals_at_zeta, + vals_at_zeta_omega, + } = plonk_verifier.evaluate_piop(piop, piop_proof, challenges); + // println!("zeta = {zeta}, q(z) = {}", vals_at_zeta[vals_at_zeta.len() - 1]); + coords.extend(vec![vec![zeta]; open_at_zeta.len()]); + polys.extend(open_at_zeta); + coords.extend(vec![vec![zeta_omega]; open_at_zeta_omega.len()]); + polys.extend(open_at_zeta_omega); + vals.extend(vals_at_zeta.into_iter().map(|v| vec![v])); + vals.extend(vals_at_zeta_omega.into_iter().map(|v| vec![v])); + } + + let mut todo = side_proof.todo; + let valid = Shplonk::>::verify_many( + &self.pcs_params.vk(), + &polys, + side_proof.pcs_proof, + &coords, + &vals, + &mut todo, + ); + valid + } +} diff --git a/w3f-ring-proof/Cargo.toml b/w3f-ring-proof/Cargo.toml index e3823b3..893c03f 100644 --- a/w3f-ring-proof/Cargo.toml +++ b/w3f-ring-proof/Cargo.toml @@ -9,15 +9,15 @@ keywords = ["cryptography", "ring-vrf"] repository = "https://github.com/w3f/ring-proof" [dependencies] +w3f-pcs.workspace = true +w3f-plonk-common.workspace = true +ark-transcript.workspace = true ark-std.workspace = true ark-ff.workspace = true ark-ec.workspace = true ark-poly.workspace = true ark-serialize.workspace = true -w3f-pcs.workspace = true rayon = { workspace = true, optional = true } -w3f-plonk-common.workspace = true -ark-transcript.workspace = true [dev-dependencies] ark-bls12-381.workspace = true @@ -49,9 +49,6 @@ parallel = [ "w3f-plonk-common/parallel", "w3f-pcs/parallel" ] -print-trace = [ - "ark-std/print-trace", - "w3f-plonk-common/print-trace" -] +print-trace = ["ark-std/print-trace"] asm = [ "w3f-pcs/asm" ] test-vectors = [ "w3f-plonk-common/test-vectors" ] diff --git a/w3f-ring-proof/src/lib.rs b/w3f-ring-proof/src/lib.rs index 0ea6dd0..02b3aad 100644 --- a/w3f-ring-proof/src/lib.rs +++ b/w3f-ring-proof/src/lib.rs @@ -1,5 +1,4 @@ #![cfg_attr(not(feature = "std"), no_std)] - use ark_ff::PrimeField; use ark_serialize::CanonicalSerialize; use ark_std::rand::RngCore; @@ -61,7 +60,6 @@ mod tests { use w3f_plonk_common::test_helpers::random_vec; - use crate::piop::FixedColumnsCommitted; use crate::ring::{Ring, RingBuilderKey}; use crate::ring_prover::RingProver; use crate::ring_verifier::RingVerifier; diff --git a/w3f-ring-proof/src/piop/mod.rs b/w3f-ring-proof/src/piop/mod.rs index 3112c65..6fd12e1 100644 --- a/w3f-ring-proof/src/piop/mod.rs +++ b/w3f-ring-proof/src/piop/mod.rs @@ -165,7 +165,8 @@ impl VerifierKey> { ring: &Ring, kzg_vk: RawKzgVerifierKey, ) -> Self { - Self::from_commitment_and_kzg_vk(FixedColumnsCommitted::from_ring(ring), kzg_vk) + let fixed_columns = FixedColumnsCommitted::from_ring(&ring); + Self::from_commitment_and_kzg_vk(fixed_columns, kzg_vk) } pub fn from_commitment_and_kzg_vk(