Compare commits

...

6 Commits

Author SHA1 Message Date
str4d d223584f86
Merge 3836b6e327 into 7df93fd855 2024-03-05 16:30:01 +08:00
Daira-Emma Hopwood 7df93fd855
Merge pull request #814 from adria0/fix/mdbook
Fix MD book generation
2024-02-26 23:50:17 +00:00
adria0 daaa638966 fix(mdbook): fix generation 2024-02-22 22:28:36 +01:00
Daira-Emma Hopwood 81729eca91
Merge pull request #809 from daira/remove-empty-halo2-crate-from-readme
Remove references to the empty `halo2` crate from the README
2024-02-06 15:25:48 +00:00
Daira-Emma Hopwood 4a8e640afd Remove references to the empty `halo2` crate from the README, and link
to the `halo2_proofs` and `halo2_gadgets` READMEs.

Signed-off-by: Daira-Emma Hopwood <daira@jacaranda.org>
2024-02-06 12:54:21 +00:00
Jack Grigg 3836b6e327 dev: Measure verifier time cost 2023-03-21 03:48:36 +00:00
6 changed files with 442 additions and 15 deletions

View File

@ -12,7 +12,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly
toolchain: '1.76.0'
override: true
# - name: Setup mdBook
@ -26,7 +26,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: install
args: mdbook --git https://github.com/HollowMan6/mdBook.git --rev 62e01b34c23b957579c04ee1b24b57814ed8a4d5
args: mdbook --git https://github.com/HollowMan6/mdBook.git --rev 5830c9555a4dc051675d17f1fcb04dd0920543e8
- name: Install mdbook-katex and mdbook-pdf
uses: actions-rs/cargo@v1
@ -40,6 +40,11 @@ jobs:
- name: Build halo2 book
run: mdbook build book/
- uses: actions-rs/toolchain@v1
with:
toolchain: nightly-2023-10-05
override: true
- name: Build latest rustdocs
uses: actions-rs/cargo@v1
with:

View File

@ -1,6 +1,9 @@
# halo2 [![Crates.io](https://img.shields.io/crates/v/halo2.svg)](https://crates.io/crates/halo2) #
# halo2
## [Documentation](https://docs.rs/halo2)
## Usage
This repository contains the [halo2_proofs](halo2_proofs/README.md) and
[halo2_gadgets](halo2_gadgets/README.md) crates, which should be used directly.
## Minimum Supported Rust Version

View File

@ -14,8 +14,6 @@ title = "The halo2 Book"
macros = "macros.txt"
renderers = ["html"]
[output.katex]
[output.html]
[output.html.print]

View File

@ -54,6 +54,7 @@ blake2b_simd = "1"
maybe-rayon = {version = "0.1.0", default-features = false}
# Developer tooling dependencies
criterion = { version = "0.3", optional = true }
plotters = { version = "0.3.0", default-features = false, optional = true }
tabbycat = { version = "0.1", features = ["attributes"], optional = true }
@ -78,6 +79,7 @@ getrandom = { version = "0.2", features = ["js"] }
[features]
default = ["batch", "multicore"]
multicore = ["maybe-rayon/threads"]
dev-cost = ["criterion"]
dev-graph = ["plotters", "tabbycat"]
test-dev-graph = [
"dev-graph",

View File

@ -3,13 +3,16 @@
use std::{
cmp,
collections::{HashMap, HashSet},
iter,
iter::{self, Sum},
marker::PhantomData,
ops::{Add, Mul},
time::{Duration, Instant},
};
use ff::{Field, PrimeField};
use group::prime::PrimeGroup;
use group::{prime::PrimeGroup, Group};
use pasta_curves::arithmetic::{CurveAffine, CurveExt};
use rand_core::OsRng;
use crate::{
circuit::{layouter::RegionColumn, Value},
@ -20,6 +23,8 @@ use crate::{
poly::Rotation,
};
mod time;
/// Measures a circuit to determine its costs, and explain what contributes to them.
#[allow(dead_code)]
#[derive(Debug)]
@ -28,8 +33,12 @@ pub struct CircuitCost<G: PrimeGroup, ConcreteCircuit: Circuit<G::Scalar>> {
k: u32,
/// Maximum degree of the circuit.
max_deg: usize,
/// Number of instance columns.
instance_columns: usize,
/// Number of advice columns.
advice_columns: usize,
/// Costs of the gate expressions.
gate_expressions: Vec<time::ExpressionCost>,
/// Number of direct queries for each column type.
instance_queries: usize,
advice_queries: usize,
@ -38,8 +47,8 @@ pub struct CircuitCost<G: PrimeGroup, ConcreteCircuit: Circuit<G::Scalar>> {
lookups: usize,
/// Number of columns in the global permutation.
permutation_cols: usize,
/// Number of distinct sets of points in the multiopening argument.
point_sets: usize,
/// Size of each distinct sets of points in the multiopening argument.
point_sets: Vec<usize>,
/// Maximum rows used over all columns
max_rows: usize,
/// Maximum rows used over all advice columns
@ -274,6 +283,27 @@ impl<G: PrimeGroup, ConcreteCircuit: Circuit<G::Scalar>> CircuitCost<G, Concrete
assert!((1 << k) >= cs.minimum_rows());
// Measure the gate expressions.
let gate_expressions = cs
.gates
.iter()
.flat_map(move |gate| {
gate.polynomials().iter().map(move |poly| {
poly.evaluate(
&|_| (0, 0, 0).into(),
&|_| panic!("virtual selectors are removed during optimization"),
&|_, _, _| (0, 0, 0).into(),
&|_, _, _| (0, 0, 0).into(),
&|_, _, _| (0, 0, 0).into(),
&|a| a,
&|a, b| a + b + (1, 0, 0).into(),
&|a, b| a + b + (0, 1, 0).into(),
&|a, _| a + (0, 0, 1).into(),
)
})
})
.collect();
// Figure out how many point sets we have due to queried cells.
let mut column_queries: HashMap<Column<Any>, HashSet<i32>> = HashMap::new();
for (c, r) in iter::empty()
@ -318,13 +348,15 @@ impl<G: PrimeGroup, ConcreteCircuit: Circuit<G::Scalar>> CircuitCost<G, Concrete
CircuitCost {
k,
max_deg,
instance_columns: cs.num_instance_columns,
advice_columns: cs.num_advice_columns,
gate_expressions,
instance_queries: cs.instance_queries.len(),
advice_queries: cs.advice_queries.len(),
fixed_queries: cs.fixed_queries.len(),
lookups: cs.lookups.len(),
permutation_cols,
point_sets: point_sets.len(),
point_sets: point_sets.into_iter().map(|set| set.len()).collect(),
_marker: PhantomData::default(),
max_rows: layout.total_rows,
max_advice_rows: layout.total_advice_rows,
@ -402,7 +434,7 @@ impl<G: PrimeGroup, ConcreteCircuit: Circuit<G::Scalar>> CircuitCost<G, Concrete
// Multiopening argument:
// - f_commitment
// - 1 eval per set of points in multiopen argument
multiopen: ProofContribution::new(1, self.point_sets),
multiopen: ProofContribution::new(1, self.point_sets.len()),
// Polycommit:
// - s_poly commitment
@ -417,7 +449,7 @@ impl<G: PrimeGroup, ConcreteCircuit: Circuit<G::Scalar>> CircuitCost<G, Concrete
}
/// (commitments, evaluations)
#[derive(Debug)]
#[derive(Clone, Copy, Debug)]
struct ProofContribution {
commitments: usize,
evaluations: usize,
@ -459,7 +491,7 @@ impl Mul<usize> for ProofContribution {
}
/// The marginal size of a Halo 2 proof, broken down into its contributing factors.
#[derive(Debug)]
#[derive(Clone, Copy, Debug)]
pub struct MarginalProofSize<G: PrimeGroup> {
instance: ProofContribution,
advice: ProofContribution,
@ -481,7 +513,7 @@ impl<G: PrimeGroup> From<MarginalProofSize<G>> for usize {
}
/// The size of a Halo 2 proof, broken down into its contributing factors.
#[derive(Debug)]
#[derive(Clone, Copy, Debug)]
pub struct ProofSize<G: PrimeGroup> {
instance: ProofContribution,
advice: ProofContribution,

View File

@ -0,0 +1,387 @@
use std::{
iter::{self, Sum},
marker::PhantomData,
ops::{Add, Mul},
time::{Duration, Instant},
};
use group::{ff::Field, prime::PrimeGroup, Group};
use pasta_curves::arithmetic::{CurveAffine, CurveExt};
use super::{CircuitCost, MarginalProofSize, ProofContribution, ProofSize};
use crate::{
plonk::Circuit,
transcript::{Blake2bRead, Transcript, TranscriptRead},
};
impl<C: CurveExt, ConcreteCircuit: Circuit<<C as Group>::Scalar>> CircuitCost<C, ConcreteCircuit> {
/// Returns the marginal verifying cost per instance of this circuit.
pub fn marginal_verifying(&self) -> MarginalVerifyingCost<C::AffineExt> {
let chunks = self.permutation_chunks();
MarginalVerifyingCost {
// Transcript:
// - the marginal proof size
proof: self.marginal_proof_size(),
// Cells:
// - 1 polynomial commitment per instance column per instance
// - 1 commitment per instance column per instance
instance_columns: self.instance_columns,
// Gates:
gates: TimeContribution::new(0, self.gate_expressions.iter().copied()),
// Lookup arguments:
// - 7 additions per lookup argument per instance
// - 6 multiplications per lookup argument per instance
lookups: TimeContribution::new(0, [(1, 1), (1, 2), (1, 1), (4, 2)].into_iter())
* self.lookups,
// Global permutation argument:
// - 5 * chunks + 3 additions and multiplications per instance
equality: TimeContribution::new(
0,
[(1, 1)]
.into_iter()
.chain((0..chunks - 1).map(|_| (1, 1)))
.chain((0..chunks).map(|_| (4 * chunks + 3, 4 * chunks + 3))),
),
_marker: PhantomData::default(),
}
}
/// Returns the verifying cost for the given number of instances of this circuit.
pub fn verifying(&self, instances: usize) -> VerifyingCost<C::AffineExt> {
let marginal = self.marginal_verifying();
VerifyingCost {
// Transcript:
// - the proof
// - marginal cost per instance
proof: self.proof_size(instances),
instance_columns: marginal.instance_columns * instances,
// - Verifying key
// - 5 challenges
// Gates:
// - marginal cost per instance
gates: marginal.gates * instances,
// Lookup arguments:
// - marginal cost per instance
lookups: marginal.lookups * instances,
// Global permutation argument:
// - marginal cost per instance
// - TODO: global cost
equality: marginal.equality * instances,
// Vanishing argument:
// - expressions + 1 commitments
// - 1 random_poly eval
vanishing: TimeContribution::new(0, [(1, 1)].into_iter()),
// Multiopening argument:
// - TODO: point set evals
// - TODO: Lagrange interpolation per point set
// - TODO: inversions
// - 2 additions and mults per point in multiopen argument
// - 2 additions per set of points in multiopen argument
// - 1 multiplication per set of points in multiopen argument
multiopen: self
.point_sets
.iter()
.map(|points| TimeContribution::new(0, (2 * points + 2, 2 * points + 1).into()))
.sum(),
// Polycommit:
// - s_poly commitment
// - inner product argument (2 * k round commitments)
// - a
// - xi
polycomm: ProofContribution::new(1 + 2 * self.k, 2),
_marker: PhantomData::default(),
}
}
}
/// The marginal time cost of verifying a specific Halo 2 proof, broken down into its
/// contributing factors.
#[derive(Clone, Debug)]
pub struct MarginalVerifyingCost<C: CurveAffine> {
proof: MarginalProofSize<C::Curve>,
instance_columns: usize,
gates: TimeContribution,
lookups: TimeContribution,
equality: TimeContribution,
_marker: PhantomData<C>,
}
impl<C: CurveAffine> MarginalVerifyingCost<C> {
fn transcript_inputs(&self) -> ProofContribution {
self.proof.instance + self.proof.advice + self.proof.lookups + self.proof.equality
}
fn expressions(&self) -> ExpressionCost {
iter::empty()
.chain(self.gates.expressions.iter())
.chain(self.lookups.expressions.iter())
.chain(self.equality.expressions.iter())
.sum()
}
/// Estimates the concrete time cost for verifying this proof.
#[cfg(feature = "dev-cost")]
pub fn estimate(&self) -> TimeCost {
TimeCost::estimate::<C>(
self.instance_columns,
self.transcript_inputs(),
self.expressions(),
)
}
/// Evaluates the concrete time cost for verifying this proof.
pub fn evaluate(&self, single_field_add: Duration, single_field_mul: Duration) -> TimeCost {
TimeCost::evaluate::<C>(
self.instance_columns,
self.transcript_inputs(),
self.expressions(),
single_field_add,
single_field_mul,
)
}
}
/// The time cost of verifying a specific Halo 2 proof, broken down into its contributing
/// factors.
#[derive(Clone, Debug)]
pub struct VerifyingCost<C: CurveAffine> {
proof: ProofSize<C::Curve>,
instance_columns: usize,
gates: TimeContribution,
lookups: TimeContribution,
equality: TimeContribution,
vanishing: TimeContribution,
multiopen: TimeContribution,
polycomm: TimeContribution,
_marker: PhantomData<C>,
}
impl<C: CurveAffine> VerifyingCost<C> {
fn transcript_inputs(&self) -> ProofContribution {
// TODO
self.proof.instance + self.proof.advice + self.proof.lookups + self.proof.equality
}
fn expressions(&self) -> ExpressionCost {
iter::empty()
.chain(self.gates.expressions.iter())
.chain(self.lookups.expressions.iter())
.chain(self.equality.expressions.iter())
.chain(self.vanishing.expressions.iter())
.chain(self.multiopen.expressions.iter())
.chain(self.polycomm.expressions.iter())
.sum()
}
/// Estimates the concrete time cost for verifying this proof.
#[cfg(feature = "dev-cost")]
pub fn estimate(&self) -> TimeCost {
TimeCost::estimate::<C>(
self.instance_columns,
self.transcript_inputs(),
self.expressions(),
)
}
/// Evaluates the concrete time cost for verifying this proof.
pub fn evaluate(&self, single_field_add: Duration, single_field_mul: Duration) -> TimeCost {
TimeCost::evaluate::<C>(
self.instance_columns,
self.transcript_inputs(),
self.expressions(),
single_field_add,
single_field_mul,
)
}
}
/// The estimated time cost of proving or verifying a specific Halo 2 circuit.
#[derive(Clone, Copy, Debug)]
pub struct TimeCost {
transcript: Duration,
expressions: Duration,
}
impl TimeCost {
#[cfg(feature = "dev-cost")]
fn estimate<C: CurveAffine>(
instance_columns: usize,
transcript_inputs: ProofContribution,
expressions: ExpressionCost,
) -> Self {
use rand_core::OsRng;
let pairs = [0; 100].map(|_| (C::Scalar::random(OsRng), C::Scalar::random(OsRng)));
let runner = |f: fn(C::Scalar, C::Scalar) -> C::Scalar| {
let start = Instant::now();
for _ in 0..100 {
for (a, b) in pairs.into_iter() {
let _ = criterion::black_box(f(a, b));
}
}
Instant::now().duration_since(start) / (100 * pairs.len() as u32)
};
let single_field_add = runner(|a, b| a + b);
let single_field_mul = runner(|a, b| a * b);
Self::evaluate::<C>(
instance_columns,
transcript_inputs,
expressions,
single_field_add,
single_field_mul,
)
}
fn evaluate<C: CurveAffine>(
instance_columns: usize,
transcript_inputs: ProofContribution,
expressions: ExpressionCost,
single_field_add: Duration,
single_field_mul: Duration,
) -> Self {
let dummy_point = C::generator();
// Transcript cost
let transcript = {
let mut transcript = Blake2bRead::init(std::io::repeat(1));
let start = Instant::now();
for _ in 0..instance_columns {
let _ = transcript.common_point(dummy_point).unwrap();
}
for _ in 0..transcript_inputs.commitments {
let _ = transcript.read_point().unwrap();
}
for _ in 0..transcript_inputs.evaluations {
let _ = transcript.read_scalar().unwrap();
}
Instant::now().duration_since(start)
};
// Expressions cost
let expressions = single_field_add * expressions.add as u32
+ single_field_mul * (expressions.mul + expressions.scale) as u32;
Self {
transcript,
expressions,
}
}
/// Returns the total estimated time cost.
pub fn total(&self) -> Duration {
self.transcript + self.expressions
}
}
#[derive(Clone, Copy, Debug)]
pub(super) struct ExpressionCost {
add: usize,
mul: usize,
scale: usize,
}
/// Use when multiplications and scalings have the same cost.
impl From<(usize, usize)> for ExpressionCost {
fn from((add, mul): (usize, usize)) -> Self {
Self { add, mul, scale: 0 }
}
}
impl From<(usize, usize, usize)> for ExpressionCost {
fn from((add, mul, scale): (usize, usize, usize)) -> Self {
Self { add, mul, scale }
}
}
impl Add for ExpressionCost {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
add: self.add + rhs.add,
mul: self.mul + rhs.mul,
scale: self.scale + rhs.scale,
}
}
}
impl<'a> Sum<&'a ExpressionCost> for ExpressionCost {
fn sum<I: Iterator<Item = &'a ExpressionCost>>(iter: I) -> Self {
iter.fold((0, 0, 0).into(), |acc, expr| acc + *expr)
}
}
#[derive(Clone, Debug)]
struct TimeContribution {
polynomial_commitments: usize,
expressions: Vec<ExpressionCost>,
}
impl TimeContribution {
fn new<E>(polynomial_commitments: usize, expressions: impl Iterator<Item = E>) -> Self
where
ExpressionCost: From<E>,
{
Self {
polynomial_commitments,
expressions: expressions.map(ExpressionCost::from).collect(),
}
}
}
impl Add for TimeContribution {
type Output = Self;
fn add(self, mut rhs: Self) -> Self::Output {
let mut expressions = self.expressions;
expressions.append(&mut rhs.expressions);
Self {
polynomial_commitments: self.polynomial_commitments + rhs.polynomial_commitments,
expressions,
}
}
}
impl Mul<usize> for TimeContribution {
type Output = Self;
fn mul(self, instances: usize) -> Self::Output {
Self {
polynomial_commitments: self.polynomial_commitments * instances,
expressions: iter::repeat(self.expressions.into_iter())
.take(instances)
.flatten()
.collect(),
}
}
}
impl Sum for TimeContribution {
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(
TimeContribution {
polynomial_commitments: 0,
expressions: vec![],
},
|acc, expr| acc + expr,
)
}
}