penumbra_proof_setup/single/mod.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
//! This module contains definition for a single circuit setup.
#![deny(clippy::unwrap_used)]
// Todo: prune public interface once we know exactly what's needed.
mod dlog;
pub(crate) mod group;
pub mod log;
mod phase1;
mod phase2;
use ark_ec::{CurveGroup, Group};
use ark_ff::Zero;
use ark_groth16::data_structures::ProvingKey;
use ark_groth16::VerifyingKey;
use ark_poly::EvaluationDomain;
use ark_poly::Radix2EvaluationDomain;
use ark_relations::r1cs::ConstraintMatrices;
use ark_serialize::CanonicalDeserialize;
use ark_serialize::CanonicalSerialize;
use decaf377::Bls12_377;
pub use dlog::Proof as DLogProof;
pub use phase1::CRSElements as Phase1CRSElements;
pub use phase1::Contribution as Phase1Contribution;
pub(crate) use phase1::LinkingProof;
pub use phase1::RawCRSElements as Phase1RawCRSElements;
pub use phase1::RawContribution as Phase1RawContribution;
pub use phase2::CRSElements as Phase2CRSElements;
pub use phase2::Contribution as Phase2Contribution;
pub use phase2::RawCRSElements as Phase2RawCRSElements;
pub use phase2::RawContribution as Phase2RawContribution;
use group::{F, G1, G2};
use anyhow::{anyhow, Result};
#[derive(Clone, Debug, CanonicalSerialize, CanonicalDeserialize)]
pub struct ExtraTransitionInformation {
/// The u polynomials evaluated at [x].
u_1: Vec<G1>,
/// The v polynomials evaluted at [x].
v_1: Vec<G1>,
/// The v polynomials evaluted at [x]_2.
v_2: Vec<G2>,
/// The p polynomials evaluated at [x]
p_1: Vec<G1>,
}
// Compute the degree associated with a given circuit.
//
// This degree can then be used for both phases.
pub fn circuit_degree(circuit: &ConstraintMatrices<F>) -> Result<usize> {
let circuit_size = circuit.num_constraints + circuit.num_instance_variables;
Radix2EvaluationDomain::<group::F>::compute_size_of_domain(circuit_size)
.ok_or_else(|| anyhow!("Circuit of size {} is too large", circuit_size))
}
/// Transition between phase1 and phase2, using the circuit.
///
/// This will also produce extra elements needed later when combining the elements
/// into the actual proving key.
pub fn transition(
phase1: &phase1::CRSElements,
circuit: &ConstraintMatrices<F>,
) -> Result<(ExtraTransitionInformation, phase2::CRSElements)> {
// To understand this function, it can be good to recap the relationship between
// R1CS constraints and QAP constraints.
//
// While we call the constaints a "circuit", they're really an R1CS system.
// The circuit contains matrices A, B, C. Each of these matrices has the same size.
// The number of columns is the number of variables in our circuit,
// along with an additional column for each constraint, representing an
// "internal variable".
// The number of rows is simply the number of constraints in our circuit.
//
// To transform the circuit into a QAP, each column of a matrix becomes a polynomial.
// For the matrix A, we have uᵢ(X), for B, vᵢ(X), and C, wᵢ(X).
// We also have a domain, a list of field elements, such that evaluting each polynomial
// on the domain produces the entries of the corresponding matrix column.
//
// Furthermore, we also pad the matrices before this transformation.
// The bottom of A is filled with:
// 1 0 0 ...
// 0 1 0 ...
// 0 0 1 ...
// .........
// based on the number of instance variables. This is to avoid
// potential malleability issues. See: https://geometry.xyz/notebook/groth16-malleability.
// B and C are simply padded with 0 to match this size.
//
// Finally, the domain might be larger than the size of these matrices,
// and so we simply add rows filled with 0 to match the size of the domain.
//
// Now, we don't need to calculate these polynomials directly, instead what we care about
// is the polynomials pᵢ(X) = β uᵢ(X) + α vᵢ(X) + wᵢ(X), evaluated at [x],
// and the evaluations [t(x)xⁱ], where t is a polynomial that's 0 everywhere over the
// domain.
//
// Our strategy here is to make use of lagrange polynomials Lⱼ(X), which
// are 0 everywhere over the domain except for the jth entry.
// Specifically, if we have the evaluations Lⱼ([x]), we can then calculate
// each pᵢ([x]) by a linear combination over these evaluations,
// using the coefficients in that matrix column.
// Note that the matrices are very sparse, so there are only a linear
// number of such operations to do, instead of a quadratic number.
//
// For calculating the lagrange polynomials, we want to avoid a quadratic
// solution. To do so, we need to exploit the structure of the domain
// we use: specifically, the fact that we can do fast fourier transforms (FFTs)
// in it. (See section 3 of https://eprint.iacr.org/2017/602 for the origin
// of most of this exposition).
//
// Our domain consists of successive powers of a root of unity: 1, ω, ω², ...,
// such that ωᵈ = 1. The number of entries in the domain is d, in total.
// This is useful, because we can quickly convert a polynomial p(X), represented
// as a table of coefficients, into an evaluation table p(ωⁱ) (i ∈ [d]).
//
// We can also use this for evaluating the lagrange polynomials.
// First, note the following equality:
// Lⱼ(X) = d⁻¹∑ᵢ (Xω⁻ʲ)ⁱ
// (because the sum over all roots of unity is 0).
// Defining:
// Pₓ(Y) := d⁻¹∑ᵢ xⁱ Yⁱ
// we can write Lⱼ(x) = Lⱼ(x) = Pₓ(ω⁻ʲ).
//
// What we want is [Lⱼ(x)] for each j. With this equality, we see that we
// can get this by doing an FFT over Pₓ(Y). This will also work even if
// the coefficients are group elements, and not scalars.
// (We also need to reverse the order of the resulting FFT).
//
// This is also equivalent to doing an inverse FFT, and not including
// the inverse of d in the polynomial.
//
// The structure of the domain also helps us with the vanishing polynomial, t.
// The polynomial (Xᵈ − 1) is 0 everywhere over the domain, and give us
// a simple expression for t(X). The evaluations [t(x)xⁱ] can then be obtained
// by using simple indexing.
//
// Ok, that was the explanation, now onto the code.
let circuit_size = circuit.num_constraints + circuit.num_instance_variables;
let domain: Radix2EvaluationDomain<F> =
Radix2EvaluationDomain::new(circuit_size).ok_or_else(|| {
anyhow!(
"Failed to create evaluation domain size (at least) {}",
circuit_size
)
})?;
let domain_size = domain.size();
// 0. Check that the phase1 degree is large enough.
if phase1.degree < domain_size {
return Err(anyhow!(
"Phase1 elements not large enough: expected >= {}, found {}",
domain_size,
phase1.degree
));
}
// 1. Get the lagrange coefficients over [x].
// 1.1. Setup a polynomial that's x^i at each coefficient.
let mut extracting_poly: Vec<_> = phase1.raw.x_1.iter().copied().take(domain_size).collect();
domain.ifft_in_place(&mut extracting_poly);
let lagrange_1 = extracting_poly;
// 2. Do the same for [x]_2.
let mut extracting_poly: Vec<_> = phase1.raw.x_2.iter().copied().take(domain_size).collect();
domain.ifft_in_place(&mut extracting_poly);
let lagrange_2 = extracting_poly;
// 3. Do the same for [αx].
let mut extracting_poly: Vec<_> = phase1
.raw
.alpha_x_1
.iter()
.copied()
.take(domain_size)
.collect();
domain.ifft_in_place(&mut extracting_poly);
let alpha_lagrange_1 = extracting_poly;
// 4. Do the same for [βx].
let mut extracting_poly: Vec<_> = phase1
.raw
.beta_x_1
.iter()
.copied()
.take(domain_size)
.collect();
domain.ifft_in_place(&mut extracting_poly);
let beta_lagrange_1 = extracting_poly;
// 5. Accumulate the p_i polynomials evaluated over [x].
// This indexing is copied from ark_groth16/r1cs_to_qap.rs.html#106.
// (I spent a full massage chair cycle thinking about this and couldn't figure out
// why exactly they do it this way, but mirroring the code we're trying to be
// compatible with is a good idea).
let qap_num_variables = (circuit.num_instance_variables - 1) + circuit.num_witness_variables;
let mut p_1 = vec![G1::zero(); qap_num_variables + 1];
// Also take this opportunity to accumulate the raw u_1, v_1, and v_2 polynomials
let mut u_1 = vec![G1::zero(); qap_num_variables + 1];
let mut v_1 = vec![G1::zero(); qap_num_variables + 1];
let mut v_2 = vec![G2::zero(); qap_num_variables + 1];
// This is where we add the identity matrix block at the end to avoid malleability
// shenanigans.
{
let start = 0;
let end = circuit.num_instance_variables;
let num_constraints = circuit.num_constraints;
// One deviation if you're reading the arkworks code is that we're modifying
// the entire p polynomial, and not u (which they call 'a'), but this effectively does
// the same thing, because the other polynomials are set to 0 at these points.
p_1[start..end]
.copy_from_slice(&beta_lagrange_1[(start + num_constraints)..(end + num_constraints)]);
// We also modify u in the same way
u_1[start..end]
.copy_from_slice(&lagrange_1[(start + num_constraints)..(end + num_constraints)])
}
// Could zip here, but this looks cleaner to me.
for i in 0..circuit.num_constraints {
for &(ref coeff, j) in &circuit.a[i] {
p_1[j] += beta_lagrange_1[i] * coeff;
u_1[j] += lagrange_1[i] * coeff;
}
for &(ref coeff, j) in &circuit.b[i] {
p_1[j] += alpha_lagrange_1[i] * coeff;
v_1[j] += lagrange_1[i] * coeff;
v_2[j] += lagrange_2[i] * coeff;
}
for &(ref coeff, j) in &circuit.c[i] {
p_1[j] += lagrange_1[i] * coeff;
}
}
// 5. Calculate the t polynomial, evaluated multiplied by successive powers.
let t: Vec<_> = (0..(domain_size - 1))
.map(|i| phase1.raw.x_1[i + domain_size] - phase1.raw.x_1[i])
.collect();
Ok((
ExtraTransitionInformation {
u_1,
v_1,
v_2,
p_1: p_1.clone(),
},
phase2::CRSElements {
raw: phase2::RawCRSElements {
delta_1: G1::generator(),
delta_2: G2::generator(),
inv_delta_p_1: p_1,
inv_delta_t_1: t,
},
},
))
}
/// Combine the outputs from all phases into a proving key.
///
/// This proving key also contains a verifying key inherently.
pub fn combine(
circuit: &ConstraintMatrices<F>,
phase1out: &Phase1CRSElements,
phase2out: &Phase2CRSElements,
extra: &ExtraTransitionInformation,
) -> ProvingKey<Bls12_377> {
let vk = VerifyingKey {
alpha_g1: phase1out.raw.alpha_1.into_affine(),
beta_g2: phase1out.raw.beta_2.into_affine(),
gamma_g2: G2::generator().into_affine(),
delta_g2: phase2out.raw.delta_2.into_affine(),
gamma_abc_g1: G1::normalize_batch(&extra.p_1[..circuit.num_instance_variables]),
};
ProvingKey {
vk,
beta_g1: phase1out.raw.beta_1.into_affine(),
delta_g1: phase2out.raw.delta_1.into_affine(),
a_query: G1::normalize_batch(&extra.u_1),
b_g1_query: G1::normalize_batch(&extra.v_1),
b_g2_query: G2::normalize_batch(&extra.v_2),
h_query: G1::normalize_batch(&phase2out.raw.inv_delta_t_1),
l_query: G1::normalize_batch(
&phase2out.raw.inv_delta_p_1[circuit.num_instance_variables..],
),
}
}
#[cfg(test)]
mod test {
use ark_ff::One;
use ark_groth16::{r1cs_to_qap::LibsnarkReduction, Groth16};
use ark_r1cs_std::{
eq::EqGadget,
fields::fp::FpVar,
prelude::{AllocVar, Boolean},
};
use ark_relations::r1cs::{self, ConstraintSynthesizer, ConstraintSystem, ConstraintSystemRef};
use ark_snark::SNARK;
use rand_core::{OsRng, SeedableRng};
use crate::single::log::Hashable;
use super::*;
#[derive(Clone, Copy, Debug)]
struct TestCircuit {
x: F,
y: F,
pub x_plus_y: F,
}
impl ConstraintSynthesizer<F> for TestCircuit {
fn generate_constraints(self, cs: ConstraintSystemRef<F>) -> r1cs::Result<()> {
// Witnesses
let x_var = FpVar::new_witness(cs.clone(), || Ok(self.x))?;
let y_var = FpVar::new_witness(cs.clone(), || Ok(self.y))?;
// Public Inputs
let x_plus_y_var = FpVar::new_input(cs, || Ok(self.x_plus_y))?;
let add_output = x_var + y_var;
x_plus_y_var.conditional_enforce_equal(&add_output, &Boolean::TRUE)
}
}
#[test]
fn test_can_generate_keys_through_ceremony() -> anyhow::Result<()> {
let circuit = TestCircuit {
x: F::one(),
y: F::one(),
x_plus_y: F::from(2u64),
};
let cs = ConstraintSystem::new_ref();
cs.set_optimization_goal(r1cs::OptimizationGoal::Constraints);
cs.set_mode(r1cs::SynthesisMode::Setup);
circuit.generate_constraints(cs.clone())?;
cs.finalize();
let matrices = cs
.to_matrices()
.ok_or_else(|| anyhow!("Failed to generate constraint matrices."))?;
let mut rng = rand_chacha::ChaChaRng::seed_from_u64(1776);
let degree = circuit_degree(&matrices)?;
let phase1root = Phase1CRSElements::root(degree);
// Doing two contributions to make sure there's not some weird bug there
let phase1contribution = Phase1Contribution::make(&mut rng, phase1root.hash(), &phase1root);
let phase1contribution = Phase1Contribution::make(
&mut rng,
phase1contribution.hash(),
&phase1contribution.new_elements,
);
let (extra, phase2root) = transition(&phase1contribution.new_elements, &matrices)?;
let phase2contribution = Phase2Contribution::make(&mut rng, phase2root.hash(), &phase2root);
let phase2contribution = Phase2Contribution::make(
&mut rng,
phase2contribution.hash(),
&phase2contribution.new_elements,
);
let pk = combine(
&matrices,
&phase1contribution.new_elements,
&phase2contribution.new_elements,
&extra,
);
let proof = Groth16::<Bls12_377, LibsnarkReduction>::prove(&pk, circuit, &mut OsRng)
.map_err(|err| anyhow!(err))?;
let public_inputs = vec![circuit.x_plus_y];
let ok = Groth16::<Bls12_377, LibsnarkReduction>::verify(&pk.vk, &public_inputs, &proof)?;
assert!(ok);
Ok(())
}
}