Compare commits

...

16 Commits

Author SHA1 Message Date
ying tong dd1ffe93dd
Merge a845b81f10 into 7df93fd855 2024-03-04 16:25:12 +05:30
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
Daira Emma Hopwood ae52332c77
Merge pull request #802 from daira/book-remove-makefile
Remove obsolete book `Makefile` and `edithtml.sh`
2023-12-19 22:46:19 +00:00
Daira Emma Hopwood 2e617ae1da Add `/book/book` to `.gitignore`.
Signed-off-by: Daira Emma Hopwood <daira@jacaranda.org>
2023-11-29 21:54:04 +00:00
Daira Emma Hopwood de1d237013 Revert "Add book/Makefile for local HTML generation."
This reverts commit 591ab49266.

Signed-off-by: Daira Emma Hopwood <daira@jacaranda.org>
2023-11-29 21:54:04 +00:00
Daira Emma Hopwood 3eb4ec5c19 Change Daira Emma's name in `authors` fields.
Signed-off-by: Daira Emma Hopwood <daira@jacaranda.org>
2023-11-29 21:54:04 +00:00
Daira Emma Hopwood 7fd2ce259e
Merge pull request #805 from zcash/check-in-lockfile
Add `Cargo.lock` to repository
2023-11-29 21:52:01 +00:00
Daira Emma Hopwood 8db248786b .gitignore: Add files generated by tests/benches and editor temporary files.
Signed-off-by: Daira Emma Hopwood <daira@jacaranda.org>
2023-11-23 14:17:31 +00:00
Daira Emma Hopwood 3cd1bed5a2 The test-dev-graph features needs plotters/ttf to be able to draw text.
Signed-off-by: Daira Emma Hopwood <daira@jacaranda.org>
2023-11-23 14:17:31 +00:00
Daira Emma Hopwood 44f7002288 Set `rust-version` to 1.60 in `{halo2,halo2_proofs,halo2_gadgets}/Cargo.toml`.
It was already 1.60 in `rust-toolchain.toml` in the workspace root, but
that is not used when building in the subdirectories.

Signed-off-by: Daira Emma Hopwood <daira@jacaranda.org>
2023-11-23 14:17:31 +00:00
Jack Grigg 9c0a6bf380 cargo update
Some dependency updates are ignored because they bump MSRV.
2023-11-22 19:14:53 +00:00
Jack Grigg 6ca3b0a6cf Add `Cargo.lock` to repository
This is currently "whatever lockfile happened to last work for str4d",
but going forward will be the lockfile we use for testing our MSRV. See
https://blog.rust-lang.org/2023/08/29/committing-lockfiles for rationale
on this change.
2023-11-22 19:07:50 +00:00
therealyingtong a845b81f10 Introduce multiset equality argument 2023-03-15 16:26:52 +07:00
17 changed files with 3192 additions and 155 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:

6
.gitignore vendored
View File

@ -1,6 +1,8 @@
/target
/halo2_gadgets/*-layout.png
/halo2_gadgets/benches/sha256_assets
**/.*.swp
**/*.rs.bk
Cargo.lock
.vscode
**/*.html
.DS_Store
/book/book

2283
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

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

@ -1,10 +0,0 @@
.PHONY: all
all:
find src -type f -a -name '*.md' |sed 's/[.]md$$/.html/g' |xargs $(MAKE)
clean:
find src -type f -a -name '*.html' -print0 |xargs -0 rm
%.html: %.md
pandoc --katex --from=markdown --to=html "$<" "--output=$@"
./edithtml.sh "$@" "$<"

View File

@ -2,7 +2,7 @@
authors = [
"Jack Grigg",
"Sean Bowe",
"Daira Hopwood",
"Daira Emma Hopwood",
"Ying Tong Lai",
]
language = "en"
@ -14,8 +14,6 @@ title = "The halo2 Book"
macros = "macros.txt"
renderers = ["html"]
[output.katex]
[output.html]
[output.html.print]

View File

@ -1,28 +0,0 @@
#!/bin/sh
cat - "$1" > "$1.prefix" <<EOF
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<title>$2</title>
<style type="text/css">
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
span.underline{text-decoration: underline;}
div.column{display: inline-block; vertical-align: top; width: 50%;}
</style>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.10.2/dist/katex.min.css" integrity="sha384-yFRtMMDnQtDRO8rLpMIKrtPCD5jdktao2TV19YiZYWMDkUR5GQZR/NOVTdquEx1j" crossorigin="anonymous">
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.10.2/dist/katex.min.js" integrity="sha384-9Nhn55MVVN0/4OFx7EE5kpFBPsEMZxKTCnA+4fqDmg12eCTqGi6+BB2LjY8brQxJ" crossorigin="anonymous"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.10.2/dist/contrib/auto-render.min.js" integrity="sha384-kWPLUVMOks5AQFrykwIup5lo0m3iMkkHrD0uJ4H5cjeGihAutqP0yW0J6dpFiVkI" crossorigin="anonymous"
onload="renderMathInElement(document.body);"></script>
</head>
<body>
EOF
cat "$1.prefix" - >"$1" <<EOF
</body>
</html>
EOF
rm -f "$1.prefix"

View File

@ -5,7 +5,7 @@ authors = [
"Jack Grigg <jack@electriccoin.co>",
]
edition = "2021"
rust-version = "1.59"
rust-version = "1.60"
description = "[BETA] Fast zero-knowledge proof-carrying data implementation with no trusted setup"
license = "MIT OR Apache-2.0"
repository = "https://github.com/zcash/halo2"

View File

@ -4,12 +4,12 @@ version = "0.3.0"
authors = [
"Sean Bowe <sean@electriccoin.co>",
"Jack Grigg <jack@electriccoin.co>",
"Daira Hopwood <daira@jacaranda.org>",
"Daira Emma Hopwood <daira@jacaranda.org>",
"Ying Tong Lai <yingtong@electriccoin.co>",
"Kris Nuttycombe <kris@electriccoin.co>",
]
edition = "2021"
rust-version = "1.59"
rust-version = "1.60"
description = "Reusable gadgets and chip implementations for Halo 2"
license = "MIT OR Apache-2.0"
repository = "https://github.com/zcash/halo2"
@ -54,6 +54,7 @@ test-dev-graph = [
"plotters",
"plotters/bitmap_backend",
"plotters/bitmap_encoder",
"plotters/ttf",
]
test-dependencies = ["proptest"]

View File

@ -4,11 +4,11 @@ version = "0.3.0"
authors = [
"Sean Bowe <sean@electriccoin.co>",
"Ying Tong Lai <yingtong@electriccoin.co>",
"Daira Hopwood <daira@electriccoin.co>",
"Daira Emma Hopwood <daira@jacaranda.org>",
"Jack Grigg <jack@electriccoin.co>",
]
edition = "2021"
rust-version = "1.59"
rust-version = "1.60"
description = """
Fast PLONK-based zero-knowledge proving system with no trusted setup
"""
@ -82,6 +82,8 @@ dev-graph = ["plotters", "tabbycat"]
test-dev-graph = [
"dev-graph",
"plotters/bitmap_backend",
"plotters/bitmap_encoder",
"plotters/ttf",
]
gadget-traces = ["backtrace"]
sanity-checks = []

View File

@ -20,6 +20,7 @@ mod circuit;
mod error;
mod keygen;
mod lookup;
mod multiset_equality;
pub(crate) mod permutation;
mod vanishing;

View File

@ -6,7 +6,7 @@ use std::{
ops::{Neg, Sub},
};
use super::{lookup, permutation, Assigned, Error};
use super::{lookup, multiset_equality, permutation, Assigned, Error};
use crate::{
circuit::{Layouter, Region, Value},
poly::Rotation,
@ -954,6 +954,11 @@ pub struct ConstraintSystem<F: Field> {
// Permutation argument for performing equality constraints
pub(crate) permutation: permutation::Argument,
// Vector of multiset equality arguments, where each corresponds to a
// sequence of unpermuted expressions and a sequence of permuted
// expressions involved in the multiset equality.
pub(crate) multisets: Vec<multiset_equality::Argument<F>>,
// Vector of lookup arguments, where each corresponds to a sequence of
// input expressions and a sequence of table expressions involved in the lookup.
pub(crate) lookups: Vec<lookup::Argument<F>>,
@ -1007,6 +1012,7 @@ impl<F: Field> Default for ConstraintSystem<F> {
num_advice_queries: Vec::new(),
instance_queries: Vec::new(),
permutation: permutation::Argument::new(),
multisets: Vec::new(),
lookups: Vec::new(),
constants: vec![],
minimum_degree: None,
@ -1054,6 +1060,32 @@ impl<F: Field> ConstraintSystem<F> {
self.permutation.add_column(column);
}
/// Add a multiset equality argument for some unpermuted expressions and
/// permuted expressions.
///
/// `multiset_map` returns a map between unpermuted expressions and the
/// permuted expfressions they need to match.
pub fn multiset_equality(
&mut self,
multiset_map: impl FnOnce(&mut VirtualCells<'_, F>) -> Vec<(Expression<F>, Expression<F>)>,
) -> usize {
let mut cells = VirtualCells::new(self);
let multiset_map = multiset_map(&mut cells);
for (original, permuted) in multiset_map.iter() {
if original.contains_simple_selector() || permuted.contains_simple_selector() {
panic!(
"expression containing simple selector supplied to multiset equality argument"
);
}
}
let index = self.multisets.len();
self.multisets
.push(multiset_equality::Argument::new(multiset_map));
index
}
/// Add a lookup argument for some input expressions and table columns.
///
/// `table_map` returns a map between input expressions and the table columns
@ -1416,6 +1448,16 @@ impl<F: Field> ConstraintSystem<F> {
.unwrap_or(1),
);
// Account for the multiset equality argument.
degree = std::cmp::max(
degree,
self.multisets
.iter()
.map(|l| l.required_degree())
.max()
.unwrap_or(1),
);
// Account for each gate to ensure our quotient polynomial is the
// correct degree and that our extended domain is the right size.
degree = std::cmp::max(

View File

@ -0,0 +1,69 @@
use ff::Field;
use super::Expression;
pub(crate) mod prover;
pub(crate) mod verifier;
#[derive(Clone, Debug)]
pub(crate) struct Argument<F: Field> {
pub original_expressions: Vec<Expression<F>>,
pub permuted_expressions: Vec<Expression<F>>,
}
impl<F: Field> Argument<F> {
/// Constructs a new multiset equality argument.
///
/// `multiset_map` is a sequence of `(original, permuted)` tuples.
pub fn new(multiset_map: Vec<(Expression<F>, Expression<F>)>) -> Self {
let (original_expressions, permuted_expressions) = multiset_map.into_iter().unzip();
Argument {
original_expressions,
permuted_expressions,
}
}
pub(crate) fn required_degree(&self) -> usize {
assert_eq!(
self.original_expressions.len(),
self.permuted_expressions.len()
);
// The first value in the permutation poly should be one.
// degree 2:
// l_0(X) * (1 - z(X)) = 0
//
// The "last" value in the permutation poly should be a boolean, for
// completeness and soundness.
// degree 3:
// l_last(X) * (z(X)^2 - z(X)) = 0
//
// Enable the permutation argument for only the rows involved.
// degree (2 + original_degree) or (2 + permuted_degree) or 3,
// whichever is larger:
// (1 - (l_last(X) + l_blind(X))) * (
// z(\omega X) (\theta^{m-1} a'_0(X) + ... + a'_{m-1}(X) + \beta)
// - z(X) (\theta^{m-1} a_0(X) + ... + a_{m-1}(X) + \beta)
// ) = 0
//
let mut original_degree = 1;
for expr in self.original_expressions.iter() {
original_degree = std::cmp::max(original_degree, expr.degree());
}
let mut permuted_degree = 1;
for expr in self.permuted_expressions.iter() {
permuted_degree = std::cmp::max(permuted_degree, expr.degree());
}
// In practice because original_degree and permuted_degree are initialized to
// one, the latter half of this max() invocation is at least 3 always,
// rendering this call pointless except to be explicit in case we change
// the initialization of original_degree/permuted_degree in the future.
std::cmp::max(
// (1 - (l_last + l_blind)) z(\omega X) (\theta^{m-1} a'_0(X) + ... + a'_{m-1}(X) + \beta)
2 + original_degree,
// (1 - (l_last + l_blind)) z(X) (\theta^{m-1} a_0(X) + ... + a_{m-1}(X) + \beta)
2 + original_degree,
)
}
}

View File

@ -0,0 +1,393 @@
use std::ops::{Mul, MulAssign};
use ff::{BatchInvert, Field};
use group::Curve;
use pasta_curves::arithmetic::{CurveAffine, FieldExt};
use rand_core::RngCore;
use super::Argument;
use crate::{
arithmetic::{eval_polynomial, parallelize},
plonk::{ChallengeBeta, ChallengeTheta, ChallengeX, Error, Expression, ProvingKey},
poly::{
self,
commitment::{Blind, Params},
multiopen::ProverQuery,
Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, Rotation,
},
transcript::{EncodedChallenge, TranscriptWrite},
};
#[derive(Debug)]
pub(in crate::plonk) struct Compressed<C: CurveAffine, Ev> {
original_cosets_compressed: poly::Ast<Ev, C::Scalar, ExtendedLagrangeCoeff>,
original_compressed: Polynomial<C::Scalar, LagrangeCoeff>,
permuted_cosets_compressed: poly::Ast<Ev, C::Scalar, ExtendedLagrangeCoeff>,
permuted_compressed: Polynomial<C::Scalar, LagrangeCoeff>,
}
#[derive(Debug)]
pub(in crate::plonk) struct Committed<C: CurveAffine, Ev> {
compressed: Compressed<C, Ev>,
product_poly: Polynomial<C::Scalar, Coeff>,
product_coset: poly::AstLeaf<Ev, ExtendedLagrangeCoeff>,
product_blind: Blind<C::Scalar>,
}
#[derive(Debug)]
pub(in crate::plonk) struct Constructed<C: CurveAffine> {
product_poly: Polynomial<C::Scalar, Coeff>,
product_blind: Blind<C::Scalar>,
}
pub(in crate::plonk) struct Evaluated<C: CurveAffine> {
constructed: Constructed<C>,
}
impl<F: FieldExt> Argument<F> {
#[allow(clippy::too_many_arguments)]
pub(in crate::plonk) fn compress_expressions<
'a,
C,
Ev: Copy + Send + Sync,
Ec: Copy + Send + Sync,
>(
&self,
domain: &EvaluationDomain<C::Scalar>,
value_evaluator: &poly::Evaluator<Ev, C::Scalar, LagrangeCoeff>,
theta: ChallengeTheta<C>,
advice_values: &'a [poly::AstLeaf<Ev, LagrangeCoeff>],
fixed_values: &'a [poly::AstLeaf<Ev, LagrangeCoeff>],
instance_values: &'a [poly::AstLeaf<Ev, LagrangeCoeff>],
advice_cosets: &'a [poly::AstLeaf<Ec, ExtendedLagrangeCoeff>],
fixed_cosets: &'a [poly::AstLeaf<Ec, ExtendedLagrangeCoeff>],
instance_cosets: &'a [poly::AstLeaf<Ec, ExtendedLagrangeCoeff>],
) -> Compressed<C, Ec>
where
C: CurveAffine<ScalarExt = F>,
C::Curve: Mul<F, Output = C::Curve> + MulAssign<F>,
{
// Closure to get values of expressions and compress them
let compress_expressions = |expressions: &[Expression<C::Scalar>]| {
// Values of expressions
let expression_values: Vec<_> = expressions
.iter()
.map(|expression| {
expression.evaluate(
&|scalar| poly::Ast::ConstantTerm(scalar),
&|_| panic!("virtual selectors are removed during optimization"),
&|query| {
fixed_values[query.column_index]
.with_rotation(query.rotation)
.into()
},
&|query| {
advice_values[query.column_index]
.with_rotation(query.rotation)
.into()
},
&|query| {
instance_values[query.column_index]
.with_rotation(query.rotation)
.into()
},
&|a| -a,
&|a, b| a + b,
&|a, b| a * b,
&|a, scalar| a * scalar,
)
})
.collect();
let cosets: Vec<_> = expressions
.iter()
.map(|expression| {
expression.evaluate(
&|scalar| poly::Ast::ConstantTerm(scalar),
&|_| panic!("virtual selectors are removed during optimization"),
&|query| {
fixed_cosets[query.column_index]
.with_rotation(query.rotation)
.into()
},
&|query| {
advice_cosets[query.column_index]
.with_rotation(query.rotation)
.into()
},
&|query| {
instance_cosets[query.column_index]
.with_rotation(query.rotation)
.into()
},
&|a| -a,
&|a, b| a + b,
&|a, b| a * b,
&|a, scalar| a * scalar,
)
})
.collect();
// Compressed version of expressions
let compressed_expressions = expression_values.iter().fold(
poly::Ast::ConstantTerm(C::Scalar::zero()),
|acc, expression| &(acc * *theta) + expression,
);
// Compressed version of cosets
let compressed_cosets = cosets.iter().fold(
poly::Ast::<_, _, ExtendedLagrangeCoeff>::ConstantTerm(C::Scalar::zero()),
|acc, eval| acc * poly::Ast::ConstantTerm(*theta) + eval.clone(),
);
(
compressed_cosets,
value_evaluator.evaluate(&compressed_expressions, domain),
)
};
let (original_cosets_compressed, original_compressed) =
compress_expressions(&self.original_expressions);
let (permuted_cosets_compressed, permuted_compressed) =
compress_expressions(&self.permuted_expressions);
Compressed {
original_cosets_compressed,
original_compressed,
permuted_cosets_compressed,
permuted_compressed,
}
}
}
impl<C: CurveAffine, Ev> Compressed<C, Ev> {
pub(in crate::plonk) fn commit_product<
E: EncodedChallenge<C>,
R: RngCore,
T: TranscriptWrite<C, E>,
>(
self,
pk: &ProvingKey<C>,
params: &Params<C>,
beta: ChallengeBeta<C>,
evaluator: &mut poly::Evaluator<Ev, C::Scalar, ExtendedLagrangeCoeff>,
mut rng: R,
transcript: &mut T,
) -> Result<Committed<C, Ev>, Error> {
let blinding_factors = pk.vk.cs.blinding_factors();
// Goal is to compute the products of fractions
//
// Numerator: (\theta^{m-1} a_0(\omega^i) + \theta^{m-2} a_1(\omega^i) + ... + \theta a_{m-2}(\omega^i) + a_{m-1}(\omega^i) + \beta)
// Denominator: (\theta^{m-1} a'_0(\omega^i) + \theta^{m-2} a'_1(\omega^i) + ... + \theta a'_{m-2}(\omega^i) + a'_{m-1}(\omega^i) + \beta)
//
// where a(X) is the compression of the original expressions in this multiset equality check,
// a'(X) is the compression of the permuted expressions,
// and i is the ith row of the expression.
let mut product = vec![C::Scalar::zero(); params.n as usize];
// Denominator uses the permuted expression
parallelize(&mut product, |product, start| {
for (product, permuted_value) in product
.iter_mut()
.zip(self.permuted_compressed[start..].iter())
{
*product = *beta + permuted_value;
}
});
// Batch invert to obtain the denominators for the product
// polynomials
product.iter_mut().batch_invert();
// Finish the computation of the entire fraction by computing the numerators
// (\theta^{m-1} a_0(\omega^i) + \theta^{m-2} a_1(\omega^i) + ... + \theta a_{m-2}(\omega^i) + a_{m-1}(\omega^i) + \beta)
parallelize(&mut product, |product, start| {
for (product, original_value) in product
.iter_mut()
.zip(self.original_compressed[start..].iter())
{
*product *= &(*beta + original_value);
}
});
// The product vector is a vector of products of fractions of the form
//
// Numerator: (\theta^{m-1} a_0(\omega^i) + \theta^{m-2} a_1(\omega^i) + ... + \theta a_{m-2}(\omega^i) + a_{m-1}(\omega^i) + \beta)
// Denominator: (\theta^{m-1} a'_0(\omega^i) + \theta^{m-2} a'_1(\omega^i) + ... + \theta a'_{m-2}(\omega^i) + a'_{m-1}(\omega^i) + \beta)
//
// where there are m original expressions and m permuted expressions,
// a_j(\omega^i)'s are the original expressions,
// a'_j(\omega^i)'s are the original expressions,
// and i is the ith row of the expression.
// Compute the evaluations of the lookup product polynomial
// over our domain, starting with z[0] = 1
let z = std::iter::once(C::Scalar::one())
.chain(product)
.scan(C::Scalar::one(), |state, cur| {
*state *= &cur;
Some(*state)
})
// Take all rows including the "last" row which should
// be a boolean (and ideally 1, else soundness is broken)
.take(params.n as usize - blinding_factors)
// Chain random blinding factors.
.chain((0..blinding_factors).map(|_| C::Scalar::random(&mut rng)))
.collect::<Vec<_>>();
assert_eq!(z.len(), params.n as usize);
let z = pk.vk.domain.lagrange_from_vec(z);
#[cfg(feature = "sanity-checks")]
// This test works only with intermediate representations in this method.
// It can be used for debugging purposes.
{
// While in Lagrange basis, check that product is correctly constructed
let u = (params.n as usize) - (blinding_factors + 1);
// l_0(X) * (1 - z(X)) = 0
assert_eq!(z[0], C::Scalar::one());
// z(\omega X) (\theta^{m-1} a'_0(X) + ... + a'_{m-1}(X) + \beta)
// - z(X) (\theta^{m-1} a_0(X) + ... + a_{m-1}(X) + \beta)
for i in 0..u {
let mut left = z[i + 1];
let permuted_value = &self.permuted_compressed[i];
left *= &(*beta + permuted_value);
let mut right = z[i];
let original_value = self.original_compressed[i];
right *= &(*beta + original_value);
assert_eq!(left, right);
}
// l_last(X) * (z(X)^2 - z(X)) = 0
// Assertion will fail only when soundness is broken, in which
// case this z[u] value will be zero. (bad!)
assert_eq!(z[u], C::Scalar::one());
}
let product_blind = Blind(C::Scalar::random(rng));
let product_commitment = params.commit_lagrange(&z, product_blind).to_affine();
let z = pk.vk.domain.lagrange_to_coeff(z);
let product_coset = evaluator.register_poly(pk.vk.domain.coeff_to_extended(z.clone()));
// Hash product commitment
transcript.write_point(product_commitment)?;
Ok(Committed::<C, _> {
compressed: self,
product_poly: z,
product_coset,
product_blind,
})
}
}
impl<'a, C: CurveAffine, Ev: Copy + Send + Sync + 'a> Committed<C, Ev> {
/// Given a Multiset equality argument with unpermuted expressions,
/// permuted expressions, and grand product polynomial, this method
/// constructs constraints that must hold between these values.
/// This method returns the constraints as a vector of ASTs for polynomials in
/// the extended evaluation domain.
pub(in crate::plonk) fn construct(
self,
beta: ChallengeBeta<C>,
l0: poly::AstLeaf<Ev, ExtendedLagrangeCoeff>,
l_blind: poly::AstLeaf<Ev, ExtendedLagrangeCoeff>,
l_last: poly::AstLeaf<Ev, ExtendedLagrangeCoeff>,
) -> (
Constructed<C>,
impl Iterator<Item = poly::Ast<Ev, C::Scalar, ExtendedLagrangeCoeff>> + 'a,
) {
let compressed = self.compressed;
let active_rows = poly::Ast::one() - (poly::Ast::from(l_last) + l_blind);
let beta = poly::Ast::ConstantTerm(*beta);
let expressions = std::iter::empty()
// l_0(X) * (1 - z(X)) = 0
.chain(Some((poly::Ast::one() - self.product_coset) * l0))
// l_last(X) * (z(X)^2 - z(X)) = 0
.chain(Some(
(poly::Ast::from(self.product_coset) * self.product_coset - self.product_coset)
* l_last,
))
// (1 - (l_last(X) + l_blind(X))) * (
// z(\omega X) (\theta^{m-1} a'_0(X) + ... + a'_{m-1}(X) + \beta)
// - z(X) (\theta^{m-1} a_0(X) + ... + a_{m-1}(X) + \beta)
// ) = 0
.chain({
// z(\omega X) (a'(X) + \beta)
let left: poly::Ast<_, _, _> = poly::Ast::<_, C::Scalar, _>::from(
self.product_coset.with_rotation(Rotation::next()),
) * (compressed.permuted_cosets_compressed
+ beta.clone());
// z(X) (\theta^{m-1} a_0(X) + ... + a_{m-1}(X) + \beta)
let right: poly::Ast<_, _, _> = poly::Ast::from(self.product_coset)
* (&compressed.original_cosets_compressed + &beta);
Some((left - right) * active_rows)
});
(
Constructed {
product_poly: self.product_poly,
product_blind: self.product_blind,
},
expressions,
)
}
}
impl<C: CurveAffine> Constructed<C> {
pub(in crate::plonk) fn evaluate<E: EncodedChallenge<C>, T: TranscriptWrite<C, E>>(
self,
pk: &ProvingKey<C>,
x: ChallengeX<C>,
transcript: &mut T,
) -> Result<Evaluated<C>, Error> {
let domain = &pk.vk.domain;
let x_next = domain.rotate_omega(*x, Rotation::next());
let product_eval = eval_polynomial(&self.product_poly, *x);
let product_next_eval = eval_polynomial(&self.product_poly, x_next);
// Hash each advice evaluation
for eval in std::iter::empty()
.chain(Some(product_eval))
.chain(Some(product_next_eval))
{
transcript.write_scalar(eval)?;
}
Ok(Evaluated { constructed: self })
}
}
impl<C: CurveAffine> Evaluated<C> {
pub(in crate::plonk) fn open<'a>(
&'a self,
pk: &'a ProvingKey<C>,
x: ChallengeX<C>,
) -> impl Iterator<Item = ProverQuery<'a, C>> + Clone {
let x_next = pk.vk.domain.rotate_omega(*x, Rotation::next());
std::iter::empty()
// Open multiset argument product commitments at x
.chain(Some(ProverQuery {
point: *x,
poly: &self.constructed.product_poly,
blind: self.constructed.product_blind,
}))
// Open multiset argument product commitments at x_next
.chain(Some(ProverQuery {
point: x_next,
poly: &self.constructed.product_poly,
blind: self.constructed.product_blind,
}))
}
}

View File

@ -0,0 +1,139 @@
use std::iter;
use super::super::{circuit::Expression, ChallengeBeta, ChallengeTheta, ChallengeX};
use super::Argument;
use crate::{
arithmetic::{CurveAffine, FieldExt},
plonk::{Error, VerifyingKey},
poly::{multiopen::VerifierQuery, Rotation},
transcript::{EncodedChallenge, TranscriptRead},
};
use ff::Field;
pub struct Committed<C: CurveAffine> {
product_commitment: C,
}
pub struct Evaluated<C: CurveAffine> {
committed: Committed<C>,
product_eval: C::Scalar,
product_next_eval: C::Scalar,
}
impl<F: FieldExt> Argument<F> {
pub(in crate::plonk) fn read_product_commitment<
C: CurveAffine,
E: EncodedChallenge<C>,
T: TranscriptRead<C, E>,
>(
self,
transcript: &mut T,
) -> Result<Committed<C>, Error> {
let product_commitment = transcript.read_point()?;
Ok(Committed { product_commitment })
}
}
impl<C: CurveAffine> Committed<C> {
pub(crate) fn evaluate<E: EncodedChallenge<C>, T: TranscriptRead<C, E>>(
self,
transcript: &mut T,
) -> Result<Evaluated<C>, Error> {
let product_eval = transcript.read_scalar()?;
let product_next_eval = transcript.read_scalar()?;
Ok(Evaluated {
committed: self,
product_eval,
product_next_eval,
})
}
}
impl<C: CurveAffine> Evaluated<C> {
#[allow(clippy::too_many_arguments)]
pub(in crate::plonk) fn expressions<'a>(
&'a self,
l_0: C::Scalar,
l_last: C::Scalar,
l_blind: C::Scalar,
argument: &'a Argument<C::Scalar>,
theta: ChallengeTheta<C>,
beta: ChallengeBeta<C>,
advice_evals: &[C::Scalar],
fixed_evals: &[C::Scalar],
instance_evals: &[C::Scalar],
) -> impl Iterator<Item = C::Scalar> + 'a {
let active_rows = C::Scalar::one() - (l_last + l_blind);
let product_expression = || {
let compress_expressions = |expressions: &[Expression<C::Scalar>]| {
expressions
.iter()
.map(|expression| {
expression.evaluate(
&|scalar| scalar,
&|_| panic!("virtual selectors are removed during optimization"),
&|query| fixed_evals[query.index],
&|query| advice_evals[query.index],
&|query| instance_evals[query.index],
&|a| -a,
&|a, b| a + &b,
&|a, b| a * &b,
&|a, scalar| a * &scalar,
)
})
.fold(C::Scalar::zero(), |acc, eval| acc * &*theta + &eval)
};
// z(\omega X) (\theta^{m-1} a'_0(X) + ... + a'_{m-1}(X) + \beta)
// - z(X) (\theta^{m-1} a_0(X) + ... + a_{m-1}(X) + \beta)
let left = self.product_next_eval
* &(compress_expressions(&argument.permuted_expressions) + &*beta);
let right = self.product_eval
* &(compress_expressions(&argument.original_expressions) + &*beta);
(left - &right) * &active_rows
};
std::iter::empty()
.chain(
// l_0(X) * (1 - z(X)) = 0
Some(l_0 * &(C::Scalar::one() - &self.product_eval)),
)
.chain(
// l_last(X) * (z(X)^2 - z(X)) = 0
Some(l_last * &(self.product_eval.square() - &self.product_eval)),
)
.chain(
// (1 - (l_last(X) + l_blind(X))) * (
// z(\omega X) (\theta^{m-1} a'_0(X) + ... + a'_{m-1}(X) + \beta)
// - z(X) (\theta^{m-1} a_0(X) + ... + a_{m-1}(X) + \beta)
// ) = 0
Some(product_expression()),
)
}
pub(in crate::plonk) fn queries<'r, 'params: 'r>(
&'r self,
vk: &'r VerifyingKey<C>,
x: ChallengeX<C>,
) -> impl Iterator<Item = VerifierQuery<'r, 'params, C>> + Clone {
let x_next = vk.domain.rotate_omega(*x, Rotation::next());
iter::empty()
// Open multiset product commitment at x
.chain(Some(VerifierQuery::new_commitment(
&self.committed.product_commitment,
*x,
self.product_eval,
)))
// Open multiset product commitment at \omega x
.chain(Some(VerifierQuery::new_commitment(
&self.committed.product_commitment,
x_next,
self.product_next_eval,
)))
}
}

View File

@ -15,7 +15,7 @@ use super::{
use crate::{
arithmetic::{eval_polynomial, CurveAffine},
circuit::Value,
plonk::Assigned,
plonk::{multiset_equality, Assigned},
poly::{
self,
commitment::{Blind, Params},
@ -420,6 +420,34 @@ pub fn create_proof<
// Sample theta challenge for keeping lookup columns linearly independent
let theta: ChallengeTheta<_> = transcript.squeeze_challenge_scalar();
let multisets = instance_values
.iter()
.zip(instance_cosets.iter())
.zip(advice_values.iter())
.zip(advice_cosets.iter())
.map(
|(((instance_values, instance_cosets), advice_values), advice_cosets)| -> Vec<_> {
pk.vk
.cs
.multisets
.iter()
.map(|multiset| {
multiset.compress_expressions(
domain,
&value_evaluator,
theta,
advice_values,
&fixed_values,
instance_values,
advice_cosets,
&fixed_cosets,
instance_cosets,
)
})
.collect::<Vec<_>>()
},
);
let lookups: Vec<Vec<lookup::prover::Permuted<C, _>>> = instance_values
.iter()
.zip(instance_cosets.iter())
@ -480,6 +508,27 @@ pub fn create_proof<
})
.collect::<Result<Vec<_>, _>>()?;
// Commit to multiset equality arguments.
let multisets: Vec<Vec<multiset_equality::prover::Committed<C, _>>> = multisets
.map(|multisets| -> Result<Vec<_>, _> {
// Construct and commit to products for each multiset
multisets
.into_iter()
.map(|multiset| {
multiset.commit_product(
pk,
params,
beta,
&mut coset_evaluator,
&mut rng,
transcript,
)
})
.collect::<Result<Vec<_>, _>>()
})
.collect::<Result<Vec<_>, _>>()?;
// Commit to lookups.
let lookups: Vec<Vec<lookup::prover::Committed<C, _>>> = lookups
.into_iter()
.map(|lookups| -> Result<Vec<_>, _> {
@ -529,6 +578,17 @@ pub fn create_proof<
})
.unzip();
let (multisets, multiset_expressions): (Vec<Vec<_>>, Vec<Vec<_>>) = multisets
.into_iter()
.map(|multisets| {
// Evaluate the h(X) polynomial's constraint system expressions for the multiset argument constraints, if any.
multisets
.into_iter()
.map(|p| p.construct(beta, l0, l_blind, l_last))
.unzip()
})
.unzip();
let (lookups, lookup_expressions): (Vec<Vec<_>>, Vec<Vec<_>>) = lookups
.into_iter()
.map(|lookups| {
@ -544,9 +604,13 @@ pub fn create_proof<
.iter()
.zip(instance_cosets.iter())
.zip(permutation_expressions.into_iter())
.zip(multiset_expressions)
.zip(lookup_expressions.into_iter())
.flat_map(
|(((advice_cosets, instance_cosets), permutation_expressions), lookup_expressions)| {
|(
(((advice_cosets, instance_cosets), permutation_expressions), multiset_expressions),
lookup_expressions,
)| {
let fixed_cosets = &fixed_cosets;
iter::empty()
// Custom constraints
@ -579,6 +643,8 @@ pub fn create_proof<
}))
// Permutation constraints, if any.
.chain(permutation_expressions.into_iter())
// Multiset constraints, if any.
.chain(multiset_expressions.into_iter().flatten())
// Lookup constraints, if any.
.chain(lookup_expressions.into_iter().flatten())
},
@ -663,6 +729,17 @@ pub fn create_proof<
.map(|permutation| -> Result<_, _> { permutation.evaluate(pk, x, transcript) })
.collect::<Result<Vec<_>, _>>()?;
// Evaluate the multisets, if any, at omega^i x.
let multisets: Vec<Vec<multiset_equality::prover::Evaluated<C>>> = multisets
.into_iter()
.map(|multisets| -> Result<Vec<_>, _> {
multisets
.into_iter()
.map(|p| p.evaluate(pk, x, transcript))
.collect::<Result<Vec<_>, _>>()
})
.collect::<Result<Vec<_>, _>>()?;
// Evaluate the lookups, if any, at omega^i x.
let lookups: Vec<Vec<lookup::prover::Evaluated<C>>> = lookups
.into_iter()
@ -674,52 +751,54 @@ pub fn create_proof<
})
.collect::<Result<Vec<_>, _>>()?;
let instances = instance
.iter()
.zip(advice.iter())
.zip(permutations.iter())
.zip(lookups.iter())
.flat_map(|(((instance, advice), permutation), lookups)| {
iter::empty()
.chain(
pk.vk
.cs
.instance_queries
.iter()
.map(move |&(column, at)| ProverQuery {
point: domain.rotate_omega(*x, at),
poly: &instance.instance_polys[column.index()],
blind: Blind::default(),
}),
)
.chain(
pk.vk
.cs
.advice_queries
.iter()
.map(move |&(column, at)| ProverQuery {
point: domain.rotate_omega(*x, at),
poly: &advice.advice_polys[column.index()],
blind: advice.advice_blinds[column.index()],
}),
)
.chain(permutation.open(pk, x))
.chain(lookups.iter().flat_map(move |p| p.open(pk, x)).into_iter())
})
.chain(
pk.vk
.cs
.fixed_queries
.iter()
.map(|&(column, at)| ProverQuery {
point: domain.rotate_omega(*x, at),
poly: &pk.fixed_polys[column.index()],
blind: Blind::default(),
}),
)
.chain(pk.permutation.open(x))
// We query the h(X) polynomial at x
.chain(vanishing.open(x));
let instances =
instance
.iter()
.zip(advice.iter())
.zip(permutations.iter())
.zip(multisets.iter())
.zip(lookups.iter())
.flat_map(
|((((instance, advice), permutation), multisets), lookups)| {
iter::empty()
.chain(pk.vk.cs.instance_queries.iter().map(move |&(column, at)| {
ProverQuery {
point: domain.rotate_omega(*x, at),
poly: &instance.instance_polys[column.index()],
blind: Blind::default(),
}
}))
.chain(pk.vk.cs.advice_queries.iter().map(move |&(column, at)| {
ProverQuery {
point: domain.rotate_omega(*x, at),
poly: &advice.advice_polys[column.index()],
blind: advice.advice_blinds[column.index()],
}
}))
.chain(permutation.open(pk, x))
.chain(
multisets
.iter()
.flat_map(move |p| p.open(pk, x))
.into_iter(),
)
.chain(lookups.iter().flat_map(move |p| p.open(pk, x)).into_iter())
},
)
.chain(
pk.vk
.cs
.fixed_queries
.iter()
.map(|&(column, at)| ProverQuery {
point: domain.rotate_omega(*x, at),
poly: &pk.fixed_polys[column.index()],
blind: Blind::default(),
}),
)
.chain(pk.permutation.open(x))
// We query the h(X) polynomial at x
.chain(vanishing.open(x));
multiopen::create_proof(params, rng, transcript, instances).map_err(|_| Error::Opening)
}

View File

@ -149,6 +149,17 @@ pub fn verify_proof<
})
.collect::<Result<Vec<_>, _>>()?;
let multisets_committed = (0..num_proofs)
.map(|_| -> Result<Vec<_>, _> {
// Hash each multiset product commitment
vk.cs
.multisets
.iter()
.map(|argument| argument.clone().read_product_commitment(transcript))
.collect::<Result<Vec<_>, _>>()
})
.collect::<Result<Vec<_>, _>>()?;
let lookups_committed = lookups_permuted
.into_iter()
.map(|lookups| {
@ -189,6 +200,16 @@ pub fn verify_proof<
.map(|permutation| permutation.evaluate(transcript))
.collect::<Result<Vec<_>, _>>()?;
let multisets_evaluated = multisets_committed
.into_iter()
.map(|multisets| -> Result<Vec<_>, _> {
multisets
.into_iter()
.map(|multiset| multiset.evaluate(transcript))
.collect::<Result<Vec<_>, _>>()
})
.collect::<Result<Vec<_>, _>>()?;
let lookups_evaluated = lookups_committed
.into_iter()
.map(|lookups| -> Result<Vec<_>, _> {
@ -221,61 +242,85 @@ pub fn verify_proof<
.iter()
.zip(instance_evals.iter())
.zip(permutations_evaluated.iter())
.zip(multisets_evaluated.iter())
.zip(lookups_evaluated.iter())
.flat_map(|(((advice_evals, instance_evals), permutation), lookups)| {
let fixed_evals = &fixed_evals;
std::iter::empty()
// Evaluate the circuit using the custom gates provided
.chain(vk.cs.gates.iter().flat_map(move |gate| {
gate.polynomials().iter().map(move |poly| {
poly.evaluate(
&|scalar| scalar,
&|_| panic!("virtual selectors are removed during optimization"),
&|query| fixed_evals[query.index],
&|query| advice_evals[query.index],
&|query| instance_evals[query.index],
&|a| -a,
&|a, b| a + &b,
&|a, b| a * &b,
&|a, scalar| a * &scalar,
)
})
}))
.chain(permutation.expressions(
vk,
&vk.cs.permutation,
&permutations_common,
advice_evals,
fixed_evals,
instance_evals,
l_0,
l_last,
l_blind,
beta,
gamma,
x,
))
.chain(
lookups
.iter()
.zip(vk.cs.lookups.iter())
.flat_map(move |(p, argument)| {
p.expressions(
l_0,
l_last,
l_blind,
argument,
theta,
beta,
gamma,
advice_evals,
fixed_evals,
instance_evals,
.flat_map(
|((((advice_evals, instance_evals), permutation), multisets), lookups)| {
let fixed_evals = &fixed_evals;
std::iter::empty()
// Evaluate the circuit using the custom gates provided
.chain(vk.cs.gates.iter().flat_map(move |gate| {
gate.polynomials().iter().map(move |poly| {
poly.evaluate(
&|scalar| scalar,
&|_| {
panic!("virtual selectors are removed during optimization")
},
&|query| fixed_evals[query.index],
&|query| advice_evals[query.index],
&|query| instance_evals[query.index],
&|a| -a,
&|a, b| a + &b,
&|a, b| a * &b,
&|a, scalar| a * &scalar,
)
})
.into_iter(),
)
});
}))
.chain(permutation.expressions(
vk,
&vk.cs.permutation,
&permutations_common,
advice_evals,
fixed_evals,
instance_evals,
l_0,
l_last,
l_blind,
beta,
gamma,
x,
))
.chain(
multisets
.iter()
.zip(vk.cs.multisets.iter())
.flat_map(move |(p, argument)| {
p.expressions(
l_0,
l_last,
l_blind,
argument,
theta,
beta,
advice_evals,
fixed_evals,
instance_evals,
)
})
.into_iter(),
)
.chain(
lookups
.iter()
.zip(vk.cs.lookups.iter())
.flat_map(move |(p, argument)| {
p.expressions(
l_0,
l_last,
l_blind,
argument,
theta,
beta,
gamma,
advice_evals,
fixed_evals,
instance_evals,
)
})
.into_iter(),
)
},
);
vanishing.verify(params, expressions, y, xn)
};
@ -286,12 +331,19 @@ pub fn verify_proof<
.zip(advice_commitments.iter())
.zip(advice_evals.iter())
.zip(permutations_evaluated.iter())
.zip(multisets_evaluated.iter())
.zip(lookups_evaluated.iter())
.flat_map(
|(
(
(((instance_commitments, instance_evals), advice_commitments), advice_evals),
permutation,
(
(
((instance_commitments, instance_evals), advice_commitments),
advice_evals,
),
permutation,
),
multisets,
),
lookups,
)| {
@ -315,6 +367,12 @@ pub fn verify_proof<
},
))
.chain(permutation.queries(vk, x))
.chain(
multisets
.iter()
.flat_map(move |p| p.queries(vk, x))
.into_iter(),
)
.chain(
lookups
.iter()