diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..03c3f27 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,14 @@ +## Engineering code of conduct 🤝 + +### Linter + +Use `clippy` to lint your rust code: + +```bash +cargo clippy +``` + +### Documentation + +We publish dev docs on every push to `GitBook`. +Check them out at https://distributed-lab.github.io/plonky2-verifier/. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..8eb801e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,41 @@ +name: "🐛 Bug Report" +description: Create a new ticket for a bug. +title: "🐛 [BUG] - " +labels: ["bug"] +body: + - type: markdown + attributes: + value: "## Thanks for filing this out ❤️!" + - type: textarea + id: description + attributes: + label: "Description" + placeholder: Short and explicit description of your incident... + validations: + required: true + - type: textarea + id: reproduction + attributes: + label: "Reproduction steps" + placeholder: How can we reproduce the issue? + validations: + required: false + - type: dropdown + id: os + attributes: + label: "OS" + description: What is the impacted environment? + multiple: true + options: + - Windows + - Linux + - Mac + validations: + required: false + - type: textarea + id: additional_context + attributes: + label: "Additional context" + placeholder: Provide any additional information. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..0086358 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..6001e51 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,29 @@ +name: "💡 Feature Request" +description: Create a new ticket for a new feature request +title: "💡 [REQUEST] - <title>" +labels: ["enhancement"] +body: + - type: markdown + attributes: + value: "## Thanks for filing this out ❤️!" + - type: textarea + id: summary + attributes: + label: "Summary" + placeholder: Provide a brief explanation of the feature... + validations: + required: true + - type: textarea + id: basic_example + attributes: + label: "Basic Example" + placeholder: Provide a basic example of how the feature would work... + validations: + required: false + - type: textarea + id: additional_context + attributes: + label: "Additional context" + placeholder: Provide any additional information. + validations: + required: false diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..4770676 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,55 @@ +name: Deploy Dev Rust Docs to GitHub Pages + +on: + push: + paths: + - "**/*.rs" # Any Rust source code files + - Cargo.toml # Cargo.toml file + - Cargo.lock # Cargo.lock file + workflow_dispatch: + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - name: Build Rust Documentation + run: | + cargo doc --no-deps --document-private-items + echo "<meta http-equiv=\"refresh\" content=\"0; url=plonky2-verifier\">" > target/doc/index.html + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: ./target/doc + + deploy: + runs-on: ubuntu-latest + needs: build + + permissions: + pages: write + id-token: write + + environment: + name: dev-docs + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..10c592b --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,135 @@ +name: Rust CI + +on: + pull_request: + branches: + - main + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +env: + CARGO_TERM_COLOR: always + +jobs: + check: + name: Check + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Cache Cargo registry + uses: actions/cache@v4 + with: + path: ~/.cargo/registry + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-registry- + + - name: Cache Cargo build + uses: actions/cache@v4 + with: + path: target + key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-build- + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + + - name: Run cargo check + uses: actions-rs/cargo@v1 + continue-on-error: false + with: + command: check + + test: + name: Test Suite + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Cache Cargo registry + uses: actions/cache@v4 + with: + path: ~/.cargo/registry + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-registry- + + - name: Cache Cargo build + uses: actions/cache@v4 + with: + path: target + key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-build- + + - name: Install nightly toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly + + - name: Install Nextest + uses: taiki-e/install-action@v2 + with: + tool: nextest + + - name: Run Nextest + run: cargo nextest run --all-features + + lints: + name: Lints + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Cache Cargo registry + uses: actions/cache@v4 + with: + path: ~/.cargo/registry + key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-registry- + + - name: Cache Cargo build + uses: actions/cache@v4 + with: + path: target + key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-build- + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + components: rustfmt, clippy + + - name: Run cargo fmt + uses: actions-rs/cargo@v1 + continue-on-error: false + with: + command: fmt + args: --all -- --check + + - name: Run cargo clippy + uses: actions-rs/cargo@v1 + continue-on-error: false + with: + command: clippy + args: --all-targets --all-features -- -D warnings diff --git a/README.md b/README.md index d3c19de..dfcfe77 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Plonky2 Verifier [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) +[![Rust CI🌌](https://github.com/distributed-lab/plonky2-verifier/actions/workflows/rust.yml/badge.svg)](https://github.com/distributed-lab/plonky2-verifier/actions/workflows/rust.yml) +[![Docs 🌌](https://github.com/distributed-lab/plonky2-verifier/actions/workflows/docs.yml/badge.svg)](https://github.com/distributed-lab/plonky2-verifier/actions/workflows/docs.yml) > A verifier for [plonky2](https://github.com/0xPolygonZero/plonky2/) proofs. diff --git a/src/lib.rs b/src/lib.rs index 00b9140..41180a3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,11 +39,6 @@ impl From<DeserializeError> for VerifyError { /// Verify the given proof `proof` and public inputs `pubs` using verification key `vk`. /// Use the given verification key `vk` to verify the proof `proof` against the public inputs `pubs`. -/// Can fail if: -/// - the proof, the pubs or the vk are not deserializable respectively as a `plonky2::plonk::proof::Proof`, -/// a `plonky2::hash::hash_types::RichField + plonky2_field::extension::Extendable` -/// and `plonky2::plonk::circuit_data::VerifierCircuitData`. -/// - the proof is not valid. pub fn verify<F, C, const D: usize>( vk: &[u8], proof: &[u8], diff --git a/tests/artifacts_generator.rs b/tests/artifacts_generator.rs index 703541c..20fa1c3 100644 --- a/tests/artifacts_generator.rs +++ b/tests/artifacts_generator.rs @@ -7,10 +7,9 @@ use plonky2::plonk::circuit_data::CircuitConfig; use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; use plonky2::util::serialization::{DefaultGateSerializer, Write}; -/// An example of using Plonky2 to prove a statement of the form -/// "I know the 100th element of the Fibonacci sequence, starting with constants a and b." -/// When a == 0 and b == 1, this is proving knowledge of the 100th (standard) Fibonacci number. -/// This example also serializes the proof, public inputs and vk to files. +/// Fibonacci circuit, taken from plonky2 examples: +/// https://github.com/0xPolygonZero/plonky2/blob/v0.2.3/plonky2/examples/fibonacci.rs +/// Saves proof, public inputs and verification key to artifacts. pub fn gen_factorial() { const D: usize = 2; type C = PoseidonGoldilocksConfig; diff --git a/tests/integration.rs b/tests/integration.rs index 81d5c22..9f266e6 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -4,61 +4,82 @@ mod artifacts_generator; use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig}; use plonky2::util::serialization::DefaultGateSerializer; use plonky2_verifier::{verify, DeserializeError, VerifyError}; +use std::path::Path; -fn load_data() -> (Vec<u8>, Vec<u8>, Vec<u8>) { - // Ensure artifacts exist by running `gen_factorial` only if needed - if !std::path::Path::new("tests/artifacts/proof.bin").exists() || - !std::path::Path::new("tests/artifacts/pubs.bin").exists() || - !std::path::Path::new("tests/artifacts/vk.bin").exists() +const PROOF_PATH: &str = "tests/artifacts/proof.bin"; +const PUBS_PATH: &str = "tests/artifacts/pubs.bin"; +const VK_PATH: &str = "tests/artifacts/vk.bin"; + +/// Proof, public inputs and verification key in serialized format. +type TestData = (Vec<u8>, Vec<u8>, Vec<u8>); + +/// Ensures artifacts are generated and loads them. +/// Returns Result to handle potential loading errors. +fn load_data() -> Result<TestData, String> { + if !Path::new(PROOF_PATH).exists() + || !Path::new(PUBS_PATH).exists() + || !Path::new(VK_PATH).exists() { println!("Generating artifacts..."); artifacts_generator::gen_factorial(); } - let proof = std::fs::read("tests/artifacts/proof.bin").unwrap(); - let pubs = std::fs::read("tests/artifacts/pubs.bin").unwrap(); - let vk = std::fs::read("tests/artifacts/vk.bin").unwrap(); - (vk, proof, pubs) + let proof = std::fs::read(PROOF_PATH).map_err(|e| format!("Failed to load proof: {}", e))?; + let pubs = std::fs::read(PUBS_PATH).map_err(|e| format!("Failed to load pubs: {}", e))?; + let vk = std::fs::read(VK_PATH).map_err(|e| format!("Failed to load vk: {}", e))?; + + Ok((vk, proof, pubs)) } +/// Helper to run `verify` with preset types. fn verify_non_generic(vk: &Vec<u8>, proof: &Vec<u8>, pubs: &Vec<u8>) -> Result<(), VerifyError> { const D: usize = 2; type C = PoseidonGoldilocksConfig; type F = <C as GenericConfig<D>>::F; - verify::<F, C, D>(vk.as_slice(), proof.as_slice(), pubs.as_slice(), &DefaultGateSerializer) + verify::<F, C, D>( + vk.as_slice(), + proof.as_slice(), + pubs.as_slice(), + &DefaultGateSerializer, + ) } #[test] fn should_verify_valid_proof() { - let (vk, proof, pubs) = load_data(); - + let (vk, proof, pubs) = load_data().expect("Failed to load data"); assert!(verify_non_generic(&vk, &proof, &pubs).is_ok()); } #[test] fn should_not_deserialize_invalid_pubs() { - let (vk, proof, mut pubs) = load_data(); + let (vk, proof, mut pubs) = load_data().expect("Failed to load data"); pubs[0] = pubs.first().unwrap().wrapping_add(1); - assert!(matches!( - verify_non_generic(&vk, &proof, &pubs), - Err(VerifyError::InvalidData { - cause: DeserializeError::InvalidProof - }) - )); + assert!( + matches!( + verify_non_generic(&vk, &proof, &pubs), + Err(VerifyError::InvalidData { + cause: DeserializeError::InvalidProof + }) + ), + "Expected an InvalidProof error when `pubs` is corrupted" + ); } #[test] fn should_not_verify_false_proof() { - let (vk, proof, mut pubs) = load_data(); - - let len = pubs.len(); - pubs[len - 1] = pubs.last().unwrap().wrapping_add(1); + let (vk, mut proof, pubs) = load_data().expect("Failed to load data"); + + let len = proof.len(); + proof[len - 1] = pubs.last().unwrap().wrapping_add(1); - assert!(matches!( - verify_non_generic(&vk, &proof, &pubs), - Err(VerifyError::Failure { .. }) - )); -} \ No newline at end of file + assert!( + matches!( + verify_non_generic(&vk, &proof, &pubs), + Err(VerifyError::Failure { .. }) + ), + "Expected a Failure error when `proof` is corrupted" + ); +}