Skip to content

Commit

Permalink
zkEVM prover backend (#1211)
Browse files Browse the repository at this point in the history
Similar to #1193, but in here I am just interested in having it working
end-to-end, at least for a few cases, so that everybody can try it and
build upon.

<!--

Please follow this protocol when creating or reviewing PRs in this
repository:

- Leave the PR as draft until review is required.
- When reviewing a PR, every reviewer should assign themselves as soon
as they
start, so that other reviewers know the PR is covered. You should not be
discouraged from reviewing a PR with assignees, but you will know it is
not
  strictly needed.
- Unless the PR is very small, help the reviewers by not making forced
pushes, so
that GitHub properly tracks what has been changed since the last review;
use
  "merge" instead of "rebase". It can be squashed after approval.
- Once the comments have been addressed, explicitly let the reviewer
know the PR
  is ready again.

-->

---------

Co-authored-by: Leo <[email protected]>
  • Loading branch information
lvella and leonardoalt authored Apr 18, 2024
1 parent b2e0bee commit ac9dcfd
Show file tree
Hide file tree
Showing 27 changed files with 507 additions and 211 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/build-cache.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ jobs:
submodules: recursive
- name: Install Rust toolchain 1.77 (with clippy and rustfmt)
run: rustup toolchain install 1.77-x86_64-unknown-linux-gnu && rustup component add clippy --toolchain 1.77-x86_64-unknown-linux-gnu && rustup component add rustfmt --toolchain 1.77-x86_64-unknown-linux-gnu
- name: Install EStarkPolygon prover dependencies
run: sudo apt-get install -y nlohmann-json3-dev libpqxx-dev nasm
- name: Lint
run: cargo clippy --all --all-targets --all-features --profile pr-tests -- -D warnings
- name: Lint
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/nightly-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ jobs:
run: rustup target add riscv32imac-unknown-none-elf --toolchain nightly-2024-02-01-x86_64-unknown-linux-gnu
- name: Install stdlib
run: rustup component add rust-src --toolchain nightly-2024-02-01-x86_64-unknown-linux-gnu
- name: Install EStarkPolygon prover dependencies
run: sudo apt-get install -y nlohmann-json3-dev libpqxx-dev nasm
- name: Install pilcom
run: git clone https://github.com/0xPolygonHermez/pilcom.git && cd pilcom && npm install
- name: Check without Halo2
Expand Down
55 changes: 51 additions & 4 deletions .github/workflows/pr-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ jobs:
${{ runner.os }}-cargo-pr-tests-
- name: Install Rust toolchain 1.77 (with clippy and rustfmt)
run: rustup toolchain install 1.77-x86_64-unknown-linux-gnu && rustup component add clippy --toolchain 1.77-x86_64-unknown-linux-gnu && rustup component add rustfmt --toolchain 1.77-x86_64-unknown-linux-gnu
- name: Install EStarkPolygon prover dependencies
run: sudo apt-get install -y nlohmann-json3-dev libpqxx-dev nasm
- name: Lint no default features
run: cargo clippy --all --all-targets --no-default-features --profile pr-tests -- -D warnings
- name: Lint all features
Expand All @@ -43,13 +45,17 @@ jobs:
- name: Build
run: cargo build --all-targets --all --all-features --profile pr-tests
- uses: taiki-e/install-action@nextest
- name: Archive EStarkPolygon prover built dependencies
run: tar --zstd -cf pil-stark-prover-deps.tar.zst target/pr-tests/build/pil-stark-prover-*/out
- name: Create tests archive
run: cargo nextest archive --archive-file tests.tar.zst --cargo-profile pr-tests --workspace --all-features
- name: Upload tests archive
- name: Upload build artifacts
uses: actions/upload-artifact@v2
with:
name: tests_archive
path: tests.tar.zst
path: |
tests.tar.zst
pil-stark-prover-deps.tar.zst
test_quick:
needs: build
Expand Down Expand Up @@ -81,10 +87,49 @@ jobs:
run: git clone https://github.com/0xPolygonHermez/pilcom.git && cd pilcom && npm install
- uses: taiki-e/install-action@nextest
- name: Run default tests
run: PILCOM=$(pwd)/pilcom/ cargo nextest run --archive-file tests.tar.zst --verbose
run: cargo nextest run --archive-file tests.tar.zst --verbose
env:
PILCOM: ${{ github.workspace }}/pilcom/
- name: Run examples
run: cargo run --example hello_world

test_estark_polygon:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Download build artifacts
uses: actions/download-artifact@v2
with:
name: tests_archive
- name: ⚡ Cache nodejs
uses: actions/cache@v3
with:
path: |
~/pilcom/node_modules
key: ${{ runner.os }}-pilcom-node-modules
- name: Install Rust toolchain 1.77 (with clippy and rustfmt)
run: rustup toolchain install 1.77-x86_64-unknown-linux-gnu && rustup component add clippy --toolchain 1.77-x86_64-unknown-linux-gnu && rustup component add rustfmt --toolchain 1.77-x86_64-unknown-linux-gnu
- name: Install nightly
run: rustup toolchain install nightly-2024-02-01-x86_64-unknown-linux-gnu
- name: Install riscv target
run: rustup target add riscv32imac-unknown-none-elf --toolchain nightly-2024-02-01-x86_64-unknown-linux-gnu
- name: Install stdlib
run: rustup component add rust-src --toolchain nightly-2024-02-01-x86_64-unknown-linux-gnu
- name: Install pilcom
run: git clone https://github.com/0xPolygonHermez/pilcom.git && cd pilcom && npm install
- name: Install EStarkPolygon prover system dependency
run: sudo apt-get install -y nlohmann-json3-dev
- uses: taiki-e/install-action@nextest
- name: Unpack EStarkPolygon built dependencies
run: tar --zstd -xf pil-stark-prover-deps.tar.zst
- name: Run EStark Polygon test
run: cargo nextest run --archive-file tests.tar.zst --verbose --run-ignored=ignored-only --no-capture -E "test(=test_vec_median_estark_polygon)"
env:
PILCOM: ${{ github.workspace }}/pilcom/

test_slow:
strategy:
matrix:
Expand Down Expand Up @@ -127,5 +172,7 @@ jobs:
elif [[ "${{ matrix.test }}" == "subset2" ]]; then
TESTS="test(=keccak) | test(=vec_median) | test(=instruction_tests::addi) | test(=many_chunks) | test(=sum_serde)"
fi
PILCOM=$(pwd)/pilcom/ cargo nextest run --archive-file tests.tar.zst --verbose --run-ignored=ignored-only --no-capture -E "$TESTS"
cargo nextest run --archive-file tests.tar.zst --verbose --run-ignored=ignored-only --no-capture -E "$TESTS"
shell: bash
env:
PILCOM: ${{ github.workspace }}/pilcom/
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ which is compiled to RISCV, then to powdr-asm and finally to powdr-PIL.
*powdr*-pil can be used to generate proofs using multiple backends, such as:

- Halo2: via [polyexen](https://github.com/Dhole/polyexen) and [snark-verifer](https://github.com/privacy-scaling-explorations/snark-verifier/)
- eSTARK: via [Eigen's starky](https://github.com/0xEigenLabs/eigen-zkvm/)
- eSTARK: via [Eigen's starky](https://github.com/0xEigenLabs/eigen-zkvm/) or via [our fork of Polygon's zkevm-prover](https://github.com/powdr-labs/zkevm-prover).
- SuperNova: ongoing work (https://github.com/powdr-labs/powdr/pull/453)

All stages are fully automatic, which means you do not need to write any
Expand Down
5 changes: 4 additions & 1 deletion backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ repository = { workspace = true }

[features]
halo2 = ["dep:powdr-halo2"]
estark-polygon = ["dep:pil-stark-prover"]

[dependencies]
powdr-ast = { path = "../ast" }
Expand All @@ -19,12 +20,14 @@ powdr-executor = { path = "../executor" }

strum = { version = "0.24.1", features = ["derive"] }
log = "0.4.17"
serde = "1.0"
serde_json = "1.0"
thiserror = "1.0.43"
starky = { git = "https://github.com/0xEigenLabs/eigen-zkvm.git", rev = "59d2152" }
pil-stark-prover = { git = "https://github.com/powdr-labs/pil-stark-prover.git", rev = "d119f0b402c37be160", optional = true }
mktemp = "0.5.0"

[dev-dependencies]
mktemp = "0.5.0"
test-log = "0.2.12"
env_logger = "0.10.0"
pretty_assertions = "1.4.0"
Expand Down
File renamed without changes.
231 changes: 231 additions & 0 deletions backend/src/estark/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
mod json_exporter;
#[cfg(feature = "estark-polygon")]
pub mod polygon_wrapper;
pub mod starky_wrapper;

use std::{
fs::File,
io::{self, BufWriter, Write},
iter::{once, repeat},
path::{Path, PathBuf},
};

use crate::{Backend, BackendFactory, Error, Proof};
use powdr_ast::analyzed::Analyzed;

use powdr_executor::witgen::WitgenCallback;
use powdr_number::{write_polys_file, DegreeType, FieldElement};
use serde::Serialize;
use starky::types::{StarkStruct, Step, PIL};

fn create_stark_struct(degree: DegreeType) -> StarkStruct {
assert!(degree > 1);
let n_bits = (DegreeType::BITS - (degree - 1).leading_zeros()) as usize;
let n_bits_ext = n_bits + 1;

let steps = (2..=n_bits_ext)
.rev()
.step_by(4)
.map(|b| Step { nBits: b })
.collect();

StarkStruct {
nBits: n_bits,
nBitsExt: n_bits_ext,
nQueries: 2,
verificationHashType: "GL".to_owned(),
steps,
}
}

/// eStark provers require a fixed column with the equivalent semantics to
/// Polygon zkEVM's `L1` column. Powdr generated PIL will always have
/// `main.first_step`, but directly given PIL may not have it. This is a fixup
/// to inject such column if it doesn't exist.
///
/// TODO Improve how this is done.
fn first_step_fixup<'a, F: FieldElement>(
pil: &'a Analyzed<F>,
fixed: &'a [(String, Vec<F>)],
) -> (PIL, Vec<(String, Vec<F>)>) {
let degree = pil.degree();

let mut pil: PIL = json_exporter::export(pil);

let mut fixed = fixed.to_vec();
if !fixed.iter().any(|(k, _)| k == "main.first_step") {
use starky::types::Reference;
pil.nConstants += 1;
pil.references.insert(
"main.first_step".to_string(),
Reference {
polType: None,
type_: "constP".to_string(),
id: fixed.len(),
polDeg: degree as usize,
isArray: false,
elementType: None,
len: None,
},
);
fixed.push((
"main.first_step".to_string(),
once(F::one())
.chain(repeat(F::zero()))
.take(degree as usize)
.collect(),
));
}

(pil, fixed)
}

struct EStarkFilesCommon<'a, F: FieldElement> {
degree: DegreeType,
pil: PIL,
fixed: Vec<(String, Vec<F>)>,
output_dir: Option<&'a Path>,
}

fn buffered_write_file<R>(
path: &Path,
do_write: impl FnOnce(&mut BufWriter<File>) -> R,
) -> Result<R, io::Error> {
let mut writer = BufWriter::new(File::create(path)?);
let result = do_write(&mut writer);
writer.flush()?;

Ok(result)
}

fn write_json_file<T: ?Sized + Serialize>(path: &Path, data: &T) -> Result<(), Error> {
buffered_write_file(path, |writer| {
serde_json::to_writer(writer, data).map_err(|e| e.to_string())
})??;

Ok(())
}

fn write_polys_bin<F: FieldElement>(
path: &Path,
constants: &[(String, Vec<F>)],
) -> Result<(), Error> {
buffered_write_file(path, |writer| write_polys_file(writer, constants))??;

Ok(())
}

impl<'a, F: FieldElement> EStarkFilesCommon<'a, F> {
fn create(
analyzed: &'a Analyzed<F>,
fixed: &'a [(String, Vec<F>)],
output_dir: Option<&'a Path>,
setup: Option<&mut dyn std::io::Read>,
verification_key: Option<&mut dyn std::io::Read>,
) -> Result<Self, Error> {
if setup.is_some() {
return Err(Error::NoSetupAvailable);
}
if verification_key.is_some() {
return Err(Error::NoVerificationAvailable);
}

// Pre-process the PIL and fixed columns.
let (pil, fixed) = first_step_fixup(analyzed, fixed);

Ok(EStarkFilesCommon {
degree: analyzed.degree(),
pil,
fixed,
output_dir,
})
}
}

struct ProverInputFilePaths {
constants: PathBuf,
commits: PathBuf,
stark_struct: PathBuf,
contraints: PathBuf,
}

impl<'a, F: FieldElement> EStarkFilesCommon<'a, F> {
/// Write the files in the EStark Polygon format.
fn write_files(
&self,
output_dir: &Path,
witness: &[(String, Vec<F>)],
) -> Result<ProverInputFilePaths, Error> {
let paths = ProverInputFilePaths {
constants: output_dir.join("constants.bin"),
commits: output_dir.join("commits.bin"),
stark_struct: output_dir.join("starkstruct.json"),
contraints: output_dir.join("constraints.json"),
};

// Write the constants.
log::info!("Writing {}.", paths.constants.to_string_lossy());
write_polys_bin(&paths.constants, &self.fixed)?;

// Write the commits.
log::info!("Writing {}.", paths.commits.to_string_lossy());
write_polys_bin(&paths.commits, witness)?;

// Write the stark struct JSON.
log::info!("Writing {}.", paths.stark_struct.to_string_lossy());
write_json_file(&paths.stark_struct, &create_stark_struct(self.degree))?;

// Write the constraints in JSON.
log::info!("Writing {}.", paths.contraints.to_string_lossy());
write_json_file(&paths.contraints, &self.pil)?;

Ok(paths)
}
}

pub struct DumpFactory;

impl<F: FieldElement> BackendFactory<F> for DumpFactory {
fn create<'a>(
&self,
analyzed: &'a Analyzed<F>,
fixed: &'a [(String, Vec<F>)],
output_dir: Option<&'a Path>,
setup: Option<&mut dyn std::io::Read>,
verification_key: Option<&mut dyn std::io::Read>,
) -> Result<Box<dyn crate::Backend<'a, F> + 'a>, Error> {
Ok(Box::new(DumpBackend(EStarkFilesCommon::create(
analyzed,
fixed,
output_dir,
setup,
verification_key,
)?)))
}
}

/// A backend that just dumps the files to the output directory.
struct DumpBackend<'a, F: FieldElement>(EStarkFilesCommon<'a, F>);

impl<'a, F: FieldElement> Backend<'a, F> for DumpBackend<'a, F> {
fn prove(
&self,
witness: &[(String, Vec<F>)],
prev_proof: Option<Proof>,
// TODO: Implement challenges
_witgen_callback: WitgenCallback<F>,
) -> Result<Proof, Error> {
if prev_proof.is_some() {
return Err(Error::NoAggregationAvailable);
}

let output_dir = self
.0
.output_dir
.ok_or(Error::BackendError("output_dir is None".to_owned()))?;

self.0.write_files(output_dir, witness)?;

Ok(Vec::new())
}
}
Loading

0 comments on commit ac9dcfd

Please sign in to comment.