diff --git a/Cargo.toml b/Cargo.toml index 72ebe25..199f256 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ members = [ # "evm-vrfier", "w3f-plonk-common", "w3f-ring-proof", - "w3f-ring-vrf-snark", +# "w3f-ring-vrf-snark", ] [workspace.dependencies] @@ -14,7 +14,7 @@ ark-ec = { version = "0.5", default-features = false } ark-poly = { version = "0.5", default-features = false } ark-serialize = { version = "0.5", default-features = false, features = ["derive"] } w3f-pcs = { version = "0.0.5", default-features = false } -w3f-plonk-common = { version = "0.0.6", default-features = false } +w3f-plonk-common = { path="w3f-plonk-common", default-features = false } rayon = { version = "1", default-features = false } ark-transcript = { version = "0.0.3", default-features = false } blake2 = { version = "0.10", default-features = false } diff --git a/w3f-plonk-common/src/domain.rs b/w3f-plonk-common/src/domain.rs index 42241dc..10c88e7 100644 --- a/w3f-plonk-common/src/domain.rs +++ b/w3f-plonk-common/src/domain.rs @@ -1,11 +1,11 @@ +use crate::FieldColumn; use ark_ff::{batch_inversion, FftField, Zero}; use ark_poly::univariate::DensePolynomial; use ark_poly::{ DenseUVPolynomial, EvaluationDomain, Evaluations, GeneralEvaluationDomain, Polynomial, }; use ark_std::{vec, vec::Vec}; - -use crate::FieldColumn; +use getrandom_or_panic::getrandom_or_panic; pub const ZK_ROWS: usize = 3; @@ -25,29 +25,29 @@ impl Domains { Self { x1, x4 } } - fn column_from_evals(&self, evals: Vec, len: usize) -> FieldColumn { - assert_eq!(evals.len(), self.x1.size()); - let evals = Evaluations::from_vec_and_domain(evals, self.x1); + 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(); let evals_4x = poly.evaluate_over_domain_by_ref(self.x4); FieldColumn { - len, poly, evals, evals_4x, + payload_len, } } - fn column_from_poly(&self, poly: DensePolynomial, len: usize) -> FieldColumn { - assert!(poly.degree() < self.x1.size()); + fn column_from_poly(&self, poly: DensePolynomial) -> FieldColumn { + debug_assert!(poly.degree() + 1 <= self.x1.size()); let evals_4x = self.amplify(&poly); let evals = evals_4x.evals.iter().step_by(4).cloned().collect(); let evals = Evaluations::from_vec_and_domain(evals, self.x1); FieldColumn { - len, poly, evals, evals_4x, + payload_len: self.x1.size(), } } @@ -71,23 +71,27 @@ pub struct Domain { impl Domain { pub fn new(n: usize, hiding: bool) -> Self { let domains = Domains::new(n); - let size = domains.x1.size(); - let capacity = if hiding { size - ZK_ROWS } else { size }; - let last_row_index = capacity - 1; - - let l_first = l_i(0, size); - let l_first = domains.column_from_evals(l_first, capacity); - let l_last = l_i(last_row_index, size); - let l_last = domains.column_from_evals(l_last, capacity); + let domain_size = domains.x1.size(); + let domain_capacity = if hiding { + domain_size - ZK_ROWS + } else { + domain_size + }; + let last_row_index = domain_capacity - 1; + + let l_first = l_i(0, domain_size); + let l_first = domains.column_from_evals(l_first, 0); + let l_last = l_i(last_row_index, domain_size); + let l_last = domains.column_from_evals(l_last, 0); let not_last_row = vanishes_on_row(last_row_index, domains.x1); - let not_last_row = domains.column_from_poly(not_last_row, capacity); + let not_last_row = domains.column_from_poly(not_last_row); let zk_rows_vanishing_poly = hiding.then(|| vanishes_on_last_3_rows(domains.x1)); Self { domains, hiding, - capacity, + capacity: domain_capacity, not_last_row, l_first, l_last, @@ -106,22 +110,23 @@ impl Domain { quotient } - pub(crate) fn column(&self, mut evals: Vec, hidden: bool) -> FieldColumn { - let len = evals.len(); - assert!(len <= self.capacity); + pub(crate) fn column(&self, mut values: Vec, hidden: bool) -> FieldColumn { + let payload_len = values.len(); + debug_assert!(payload_len <= self.capacity); + values.resize(self.capacity, F::zero()); if self.hiding && hidden && !cfg!(feature = "test-vectors") { - evals.resize(self.capacity, F::zero()); - evals.resize_with(self.domains.x1.size(), || { - F::rand(&mut getrandom_or_panic::getrandom_or_panic()) - }); + values.resize_with( + self.domains.x1.size(), + || F::rand(&mut getrandom_or_panic()), + ); } else { - evals.resize(self.domains.x1.size(), F::zero()); + values.resize(self.domains.x1.size(), F::zero()); } - self.domains.column_from_evals(evals, len) + self.domains.column_from_evals(values, payload_len) } - pub fn private_column(&self, evals: Vec) -> FieldColumn { - self.column(evals, true) + pub fn private_column(&self, values: Vec) -> FieldColumn { + self.column(values, true) } // public column diff --git a/w3f-plonk-common/src/gadgets/booleanity.rs b/w3f-plonk-common/src/gadgets/booleanity.rs index ace75da..fabd43b 100644 --- a/w3f-plonk-common/src/gadgets/booleanity.rs +++ b/w3f-plonk-common/src/gadgets/booleanity.rs @@ -1,6 +1,6 @@ use ark_ff::{FftField, Field, Zero}; use ark_poly::univariate::DensePolynomial; -use ark_poly::{Evaluations, GeneralEvaluationDomain}; +use ark_poly::{Evaluations, GeneralEvaluationDomain, Polynomial}; use ark_std::{vec, vec::Vec}; @@ -23,9 +23,17 @@ impl BitColumn { let col = domain.private_column(bits_as_field_elements); Self { bits, col } } + + pub fn as_poly(&self) -> &DensePolynomial { + self.col.as_poly() + } + + pub fn evaluate(&self, z: &F) -> F { + self.as_poly().evaluate(z) + } } -impl Column for BitColumn { +impl Column for BitColumn { fn domain(&self) -> GeneralEvaluationDomain { self.col.domain() } @@ -34,8 +42,8 @@ impl Column for BitColumn { self.col.domain_4x() } - fn as_poly(&self) -> &DensePolynomial { - self.col.as_poly() + fn payload(&self) -> &[bool] { + self.bits.as_slice() } } diff --git a/w3f-plonk-common/src/gadgets/column_sum.rs b/w3f-plonk-common/src/gadgets/column_sum.rs index 0e841ec..836f2e5 100644 --- a/w3f-plonk-common/src/gadgets/column_sum.rs +++ b/w3f-plonk-common/src/gadgets/column_sum.rs @@ -35,8 +35,9 @@ pub struct ColumnSumEvals { impl ColumnSumPolys { pub fn init(col: FieldColumn, domain: &Domain) -> Self { - assert_eq!(col.len, domain.capacity - 1); // last element is not constrained - let partial_sums = Self::partial_sums(col.vals()); + // we need an extra slot to seed the partial sums acc with `0`. + debug_assert_eq!(col.payload_len(), domain.capacity - 1); + let partial_sums = Self::partial_sums(col.payload()); let mut acc = vec![F::zero()]; acc.extend(partial_sums); let acc = domain.private_column(acc); diff --git a/w3f-plonk-common/src/gadgets/ec/mod.rs b/w3f-plonk-common/src/gadgets/ec/mod.rs index f99dcb9..0e4b8b7 100644 --- a/w3f-plonk-common/src/gadgets/ec/mod.rs +++ b/w3f-plonk-common/src/gadgets/ec/mod.rs @@ -3,7 +3,7 @@ use crate::gadgets::booleanity::BitColumn; use crate::{Column, FieldColumn}; use ark_ec::{AffineRepr, CurveGroup}; use ark_ff::{FftField, Field}; - +use ark_poly::GeneralEvaluationDomain; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ark_std::marker::PhantomData; use ark_std::vec::Vec; @@ -14,7 +14,6 @@ pub mod te_doubling; // A vec of affine points from the prime-order subgroup of the curve whose base field enables FFTs, // and its convenience representation as columns of coordinates over the curve's base field. - #[derive(Clone, CanonicalSerialize, CanonicalDeserialize)] pub struct AffineColumn> { points: Vec

, @@ -43,6 +42,20 @@ impl> AffineColumn { } } +impl> Column for AffineColumn { + fn domain(&self) -> GeneralEvaluationDomain { + self.xs.domain() + } + + fn domain_4x(&self) -> GeneralEvaluationDomain { + self.xs.domain_4x() + } + + fn payload(&self) -> &[P] { + &self.points + } +} + // Conditional affine addition: // if the bit is set for a point, add the point to the acc and store, // otherwise copy the acc value @@ -53,7 +66,6 @@ pub struct CondAdd> { not_last: FieldColumn, // Accumulates the (conditional) rolling sum of the points pub acc: AffineColumn, - pub result: P, } impl> CondAdd @@ -70,8 +82,8 @@ where seed: P, domain: &Domain, ) -> Self { - assert_eq!(bitmask.bits.len(), domain.capacity - 1); - // assert_eq!(points.points.len(), domain.capacity - 1); //TODO + debug_assert_eq!(bitmask.payload_len(), domain.capacity - 1); + debug_assert_eq!(points.payload_len(), domain.capacity - 1); let not_last = domain.not_last_row.clone(); let mut projective_acc = seed.into_group(); let projective_points: Vec<_> = bitmask @@ -88,17 +100,13 @@ where let mut acc = Vec::with_capacity(projective_points.len() + 1); acc.push(seed); acc.extend(P::Group::normalize_batch(&projective_points)); - let init_plus_result = acc.last().unwrap(); - let result = init_plus_result.into_group() - seed.into_group(); - let result = result.into_affine(); let acc = AffineColumn::private_column(acc, domain); - + debug_assert_eq!(acc.payload_len(), domain.capacity); Self { bitmask, points, acc, not_last, - result, } } @@ -111,6 +119,20 @@ where _phantom: PhantomData, } } + + pub fn seed(&self) -> P { + self.acc.payload()[0] + } + + pub fn seed_plus_sum(&self) -> P { + let len = self.acc.payload_len(); + self.acc.payload()[len - 1] + } + + pub fn result(&self) -> P { + let sum = self.seed_plus_sum() - self.seed(); + sum.into_affine() + } } pub struct CondAddValues> { diff --git a/w3f-plonk-common/src/gadgets/fixed_cells.rs b/w3f-plonk-common/src/gadgets/fixed_cells.rs index 56a9fd8..1455d9b 100644 --- a/w3f-plonk-common/src/gadgets/fixed_cells.rs +++ b/w3f-plonk-common/src/gadgets/fixed_cells.rs @@ -24,7 +24,7 @@ pub struct FixedCellsValues { impl FixedCells { pub fn init(col: FieldColumn, domain: &Domain) -> Self { - assert_eq!(col.len, domain.capacity); + debug_assert_eq!(col.payload_len(), domain.capacity); let l_first = domain.l_first.clone(); let l_last = domain.l_last.clone(); Self { @@ -35,7 +35,7 @@ impl FixedCells { } pub fn constraints(&self) -> Vec> { - let domain_capacity = self.col.len; // that's an ugly way to learn the capacity, but we've asserted it above. + 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::constraint_cell(&self.col, &self.l_last, domain_capacity - 1); vec![c] diff --git a/w3f-plonk-common/src/gadgets/inner_prod.rs b/w3f-plonk-common/src/gadgets/inner_prod.rs index f977912..ca05ec3 100644 --- a/w3f-plonk-common/src/gadgets/inner_prod.rs +++ b/w3f-plonk-common/src/gadgets/inner_prod.rs @@ -24,9 +24,10 @@ pub struct InnerProdValues { impl InnerProd { pub fn init(a: FieldColumn, b: FieldColumn, domain: &Domain) -> Self { - assert_eq!(a.len, domain.capacity - 1); // last element is not constrained - assert_eq!(b.len, domain.capacity - 1); // last element is not constrained - let inner_prods = Self::partial_inner_prods(a.vals(), b.vals()); + // 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); let acc = domain.private_column(acc); diff --git a/w3f-plonk-common/src/lib.rs b/w3f-plonk-common/src/lib.rs index 8ff8dd1..ae3ac86 100644 --- a/w3f-plonk-common/src/lib.rs +++ b/w3f-plonk-common/src/lib.rs @@ -18,25 +18,24 @@ pub mod test_helpers; pub mod transcript; pub mod verifier; -pub trait Column { +pub trait Column { fn domain(&self) -> GeneralEvaluationDomain; fn domain_4x(&self) -> GeneralEvaluationDomain; - fn as_poly(&self) -> &DensePolynomial; - fn size(&self) -> usize { - self.domain().size() - } - fn evaluate(&self, z: &F) -> F { - self.as_poly().evaluate(z) + fn payload(&self) -> &[V]; + fn payload_len(&self) -> usize { + self.payload().len() } } #[derive(Clone, CanonicalSerialize, CanonicalDeserialize)] pub struct FieldColumn { - // actual (constrained) len of the input in evaluation form - pub len: usize, pub poly: DensePolynomial, pub evals: Evaluations, pub evals_4x: Evaluations, + // 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, } impl FieldColumn { @@ -46,12 +45,16 @@ impl FieldColumn { Evaluations::from_vec_and_domain(evals_4x, self.domain_4x()) } - pub fn vals(&self) -> &[F] { - &self.evals.evals[..self.len] + pub fn as_poly(&self) -> &DensePolynomial { + &self.poly + } + + pub fn evaluate(&self, z: &F) -> F { + self.as_poly().evaluate(z) } } -impl Column for FieldColumn { +impl Column for FieldColumn { fn domain(&self) -> GeneralEvaluationDomain { self.evals.domain() } @@ -60,8 +63,8 @@ impl Column for FieldColumn { self.evals_4x.domain() } - fn as_poly(&self) -> &DensePolynomial { - &self.poly + fn payload(&self) -> &[F] { + &self.evals.evals[..self.payload_len] } } diff --git a/w3f-ring-proof/src/lib.rs b/w3f-ring-proof/src/lib.rs index d2e355e..4c3ae5f 100644 --- a/w3f-ring-proof/src/lib.rs +++ b/w3f-ring-proof/src/lib.rs @@ -82,38 +82,51 @@ mod tests { let pks = random_vec::(keyset_size, rng); let (prover_key, verifier_key) = index::<_, CS, _>(&pcs_params, &piop_params, &pks); - let t_prove = start_timer!(|| "Prove"); + let prover = RingProver::init( + prover_key.clone(), + piop_params.clone(), + 0, + ArkTranscript::new(b"w3f-ring-proof-test"), + ); + + let ring_verifier = RingVerifier::init( + verifier_key, + piop_params.clone(), + ArkTranscript::new(b"w3f-ring-proof-test"), + ); + let t_prove = start_timer!(|| { + format!("Proving {batch_size} KZG ring-proofs with plonk, domain={domain_size}, max_keys={keyset_size}") + }); let claims: Vec<(EdwardsAffine, RingProof)> = (0..batch_size) .map(|_| { - let prover_idx = rng.gen_range(0..keyset_size); - let prover = RingProver::init( - prover_key.clone(), - piop_params.clone(), - prover_idx, - ArkTranscript::new(b"w3f-ring-proof-test"), - ); - let prover_pk = pks[prover_idx].clone(); - let blinding_factor = Fr::rand(rng); - let blinded_pk = prover_pk + piop_params.h.mul(blinding_factor); - let blinded_pk = blinded_pk.into_affine(); - let proof = prover.prove(blinding_factor); - (blinded_pk, proof) + let pk_idx = rng.gen_range(0..keyset_size); + let r = Fr::rand(rng); + let (blinded_pk, mem_proof) = prover.rerandomize_pk(pk_idx, r); + assert_eq!(blinded_pk, piop_params.blind_pk(pks[pk_idx], r)); + (blinded_pk, mem_proof) }) .collect(); end_timer!(t_prove); - let ring_verifier = RingVerifier::init( - verifier_key, - piop_params, - ArkTranscript::new(b"w3f-ring-proof-test"), - ); - let t_verify = start_timer!(|| "Verify"); + let t_verify = + start_timer!(|| format!("Verifying {batch_size} KZG ring-proofs with plonk")); let (blinded_pks, proofs) = claims.iter().cloned().unzip(); assert!(ring_verifier.verify_batch(proofs, blinded_pks)); end_timer!(t_verify); (ring_verifier, claims) } + #[test] + // cargo test test_ring_proof_kzg --release --features="print-trace" -- --show-output + fn test_ring_proof_kzg() { + _test_ring_proof::>(2usize.pow(9), 1); + } + + #[test] + fn test_ring_proof_id() { + _test_ring_proof::(2usize.pow(10), 1); + } + #[test] fn test_lagrangian_commitment() { let rng = &mut test_rng(); @@ -154,7 +167,7 @@ mod tests { (pcs_params, piop_params) } - // cargo test test_ring_proof_kzg --release --features="print-trace" -- --show-output + // cargo test test_ring_proof_batch_kzg_verification --release --features="print-trace" -- --show-output // // Batch vs sequential verification times (ms): // @@ -173,18 +186,15 @@ mod tests { // Sequential verification scales linearly with proof count. // Batch verification scales sub-linearly. #[test] - fn test_ring_proof_kzg() { - let batch_size: usize = 16; - let (verifier, claims) = _test_ring_proof::>(2usize.pow(10), batch_size); - let t_verify_batch = start_timer!(|| format!("Verify Batch KZG (batch={batch_size})")); + fn test_ring_proof_batch_kzg_verification() { + let batch_size: usize = 2; + let domain_size = 2usize.pow(9); + let (verifier, claims) = _test_ring_proof::>(domain_size, batch_size); let (blinded_pks, proofs) = claims.into_iter().unzip(); + let t_batch_verify = + start_timer!(|| format!("Batch-verifying {batch_size} KZG ring-proofs with plonk")); assert!(verifier.verify_batch_kzg(proofs, blinded_pks)); - end_timer!(t_verify_batch); - } - - #[test] - fn test_ring_proof_id() { - _test_ring_proof::(2usize.pow(10), 1); + end_timer!(t_batch_verify); } #[test] diff --git a/w3f-ring-proof/src/piop/mod.rs b/w3f-ring-proof/src/piop/mod.rs index 9e2d1ba..0ce9745 100644 --- a/w3f-ring-proof/src/piop/mod.rs +++ b/w3f-ring-proof/src/piop/mod.rs @@ -13,7 +13,7 @@ use w3f_pcs::pcs::{Commitment, PcsParams, PCS}; pub(crate) use prover::PiopProver; pub(crate) use verifier::PiopVerifier; use w3f_plonk_common::gadgets::ec::AffineColumn; -use w3f_plonk_common::{Column, ColumnsCommited, ColumnsEvaluated, FieldColumn}; +use w3f_plonk_common::{ColumnsCommited, ColumnsEvaluated, FieldColumn}; use crate::ring::Ring; use crate::PiopParams; diff --git a/w3f-ring-proof/src/piop/params.rs b/w3f-ring-proof/src/piop/params.rs index 22bd21e..f882e74 100644 --- a/w3f-ring-proof/src/piop/params.rs +++ b/w3f-ring-proof/src/piop/params.rs @@ -96,6 +96,11 @@ impl> PiopParams { ] .concat() } + + pub fn blind_pk(&self, pk_k: Affine, blinding: Curve::ScalarField) -> Affine { + let blinded_pk = pk_k + self.h * blinding; + blinded_pk.into_affine() + } } #[cfg(test)] diff --git a/w3f-ring-proof/src/piop/prover.rs b/w3f-ring-proof/src/piop/prover.rs index 07267c3..a67d8cb 100644 --- a/w3f-ring-proof/src/piop/prover.rs +++ b/w3f-ring-proof/src/piop/prover.rs @@ -18,7 +18,7 @@ use w3f_plonk_common::gadgets::fixed_cells::FixedCells; use w3f_plonk_common::gadgets::inner_prod::InnerProd; use w3f_plonk_common::gadgets::ProverGadget; use w3f_plonk_common::piop::ProverPiop; -use w3f_plonk_common::{Column, FieldColumn}; +use w3f_plonk_common::FieldColumn; // The 'table': columns representing the execution trace of the computation // and the constraints -- polynomials that vanish on every 2 consecutive rows. @@ -175,6 +175,6 @@ where } fn result(&self) -> Self::Instance { - self.cond_add.result + self.cond_add.result() } } diff --git a/w3f-ring-proof/src/ring_prover.rs b/w3f-ring-proof/src/ring_prover.rs index ac4db81..f76ae3c 100644 --- a/w3f-ring-proof/src/ring_prover.rs +++ b/w3f-ring-proof/src/ring_prover.rs @@ -2,7 +2,7 @@ use ark_ec::twisted_edwards::{Affine, TECurveConfig}; use ark_ff::PrimeField; use ark_std::{end_timer, start_timer}; use w3f_pcs::pcs::PCS; - +use w3f_plonk_common::piop::ProverPiop; use w3f_plonk_common::prover::PlonkProver; use w3f_plonk_common::transcript::PlonkTranscript; @@ -19,6 +19,9 @@ where { piop_params: PiopParams, fixed_columns: FixedColumns>, + // TODO: We could have a prover that as an optimization stores the commitment to the part of the trace + // TODO: that depends on the prover's index but not the blinding. That would save some computation, + // TODO: but the quotient is `O(ring-size)` anyway. k: usize, plonk_prover: PlonkProver, } @@ -59,6 +62,19 @@ where self.plonk_prover.prove(piop) } + /// Proof membership of `C_k`, given its index `k`, in the ring `pk.fixed_columns.points` identified by + /// `vk.fixed_columns_committed.points` and re-randomize the `C_k` to `C' = C_k + rH` with the given `r`. + pub fn rerandomize_pk( + &self, + k: usize, + r: Curve::ScalarField, + ) -> (Affine, RingProof) { + let piop = PiopProver::build(&self.piop_params, self.fixed_columns.clone(), k, r); + let blinded_pk = as ProverPiop>::result(&piop); + let proof = self.plonk_prover.prove(piop); + (blinded_pk, proof) + } + pub fn piop_params(&self) -> &PiopParams { &self.piop_params }