diff --git a/.github/workflows/CI-ci-remote.yml b/.github/workflows/CI-ci-remote.yml new file mode 100644 index 0000000..5a80a2d --- /dev/null +++ b/.github/workflows/CI-ci-remote.yml @@ -0,0 +1,42 @@ +name: CI-build-test-lint-fmt-deps + +run-name: "Workflow performing CI steps: build, testing, check format, check linting, check headers and check dependencies" + +on: + pull_request: + types: [opened, synchronize] + push: + branches: + - main + workflow_dispatch: + +env: + RUST_BACKTRACE: 1 + +jobs: + build-test-check: + runs-on: ubuntu-latest + steps: + - name: Checkout branch + uses: actions/checkout@v4 + - name: Install cargo-make + uses: actions-rs/cargo@v1 + with: + command: install + args: --debug cargo-make + - name: Run CI full (stable) + uses: actions-rs/cargo@v1 + with: + toolchain: stable + command: make + args: ci-remote + - name: Install latest nightly + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + - name: Run CI check-deps only (nightly) + uses: actions-rs/cargo@v1 + with: + toolchain: nightly + command: make + args: udeps \ No newline at end of file diff --git a/.github/workflows/CI-coverage.yml b/.github/workflows/CI-coverage.yml new file mode 100644 index 0000000..dc473b5 --- /dev/null +++ b/.github/workflows/CI-coverage.yml @@ -0,0 +1,41 @@ +name: CI-coverage + +run-name: "Workflow performing CI step: coverage" + +on: + pull_request: + types: [opened, synchronize] + push: + branches: + - main + workflow_dispatch: + +env: + RUST_BACKTRACE: 1 + +jobs: + coverage: + runs-on: ubuntu-latest + steps: + - name: Checkout branch + uses: actions/checkout@v4 + - name: Install coverage tools + run: | + cargo install --force cargo-llvm-cov + - name: Coverage tasks + run: | + cargo llvm-cov --workspace --lcov --output-path lcov.info + cargo llvm-cov report --json --output-path coverage_report.json --summary-only + cargo llvm-cov report > coverage-summary.txt + cat coverage-summary.txt + - name: Upload json summary as Artifact + uses: actions/upload-artifact@v4 + with: + name: coverage-output + path: ./coverage_report.json + if-no-files-found: warn + retention-days: 1 + compression-level: 0 + overwrite: true + # It is also possible to upload the generated lcov.info for later use with Codecov + # (see https://llvm.org/docs/CommandGuide/llvm-profdata.html) diff --git a/.github/workflows/CI-rustdoc.yml b/.github/workflows/CI-rustdoc.yml new file mode 100644 index 0000000..0cac622 --- /dev/null +++ b/.github/workflows/CI-rustdoc.yml @@ -0,0 +1,50 @@ +name: CI-rustdoc + +run-name: "Rust doc generation" + +on: + push: + tags: + - v[0-9]+.[0-9]+.[0-9]+* + workflow_dispatch: + +env: + RUST_BACKTRACE: 1 + +jobs: + rustdoc-generation: + runs-on: ubuntu-latest + steps: + - name: Checkout branch + uses: actions/checkout@v4 + + - name: Generate Rust documentation + run: cargo doc --no-deps + + - name: Remove lock file + run: rm target/doc/.lock + + - name: Add redirect + run: echo '' > target/doc/index.html + + - name: Upload documentation + uses: actions/upload-pages-artifact@v3 + with: + path: target/doc + + rustdoc-deployment: + needs: rustdoc-generation + + permissions: + pages: write + id-token: write + + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..32a86fc --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +Cargo.lock +lcov.info diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0ff1aa9 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "proof-of-sql-verifier" +version = "0.1.0" +edition = "2021" + +[dependencies] +ark-serialize = { version = "0.4.0", default-features = false } +bincode = "1" +blitzar = { version = "3.1.0", default-features = false, optional = true } +curve25519-dalek = { version = "4", optional = true } +proof-of-sql = { version = "0.15.0", default-features = false } +proof-of-sql-parser = { version = "0.15.0" } + +[dev-dependencies] +proof-of-sql = { version = "0.15.0", default-features = false, features = ["test"] } + +[features] +inner-product = ["dep:blitzar", "dep:curve25519-dalek", "proof-of-sql/blitzar"] diff --git a/HEADER-APACHE2 b/HEADER-APACHE2 new file mode 100644 index 0000000..d4fdff5 --- /dev/null +++ b/HEADER-APACHE2 @@ -0,0 +1,14 @@ +// Copyright 2024, Horizen Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. diff --git a/LICENSE-APACHE2 b/LICENSE-APACHE2 new file mode 100644 index 0000000..fbb0616 --- /dev/null +++ b/LICENSE-APACHE2 @@ -0,0 +1,211 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + NOTE + +Individual files contain the following tag instead of the full license +text. + + SPDX-License-Identifier: Apache-2.0 + +This enables machine processing of license information based on the SPDX +License Identifiers that are here available: http://spdx.org/licenses/ \ No newline at end of file diff --git a/Makefile.toml b/Makefile.toml new file mode 100644 index 0000000..92c31eb --- /dev/null +++ b/Makefile.toml @@ -0,0 +1,79 @@ +[config] +default_to_workspace = false + +[tasks.ignore-members] +workspace = false + +[tasks.clean] +command = "cargo" +args = ["clean"] + +[tasks.build] +dependencies = ["clean"] +command = "cargo" +args = ["build"] + +[tasks.test] +dependencies = ["clean"] +command = "cargo" +args = ["test"] + +[tasks.format_inst] +[tasks.format-inst] +install_crate = { crate_name = "rustfmt", rustup_component_name = "rustfmt", binary = "rustfmt", test_arg = "--help" } + +[tasks.format] +dependencies = ["format-inst"] +command = "cargo" +args = ["fmt"] + +[tasks.format-check] +dependencies = ["format-inst"] +command = "cargo" +args = ["fmt", "--check"] + +[tasks.clippy-inst] +install_crate = { crate_name = "clippy", rustup_component_name = "clippy", binary = "clippy", test_arg = "--help" } + +[tasks.clippy] +dependencies = ["clippy-inst"] +command = "cargo" +args = ["clippy", "--", "--deny", "warnings"] + +[tasks.header-add] +script = { file = "./scripts/add_header_if_missing.sh" } +args = ["HEADER-APACHE2", "./**/*.rs"] + +[tasks.header-check] +env = { CHECK_DIRTY = "true", DRY_RUN = "true" } +run_task = "header-add" + +[tasks.audit-inst] +command = "cargo" +args = ["install", "cargo-audit"] + +[tasks.audit] +dependencies = ["audit-inst"] +command = "cargo" +args = ["audit"] + +[tasks.cov] +command = "cargo" +args = ["llvm-cov", "--workspace", "--lcov", "--output-path", "lcov.info"] + +[tasks.udeps-inst] +command = "cargo" +args = ["install", "cargo-udeps", "--locked"] + +[tasks.udeps] +dependencies = ["udeps-inst"] +command = "cargo" +args = ["udeps", "--all-targets"] + + + +[tasks.ci] +dependencies = ["build", "test", "format", "header-add", "clippy", "audit"] + +[tasks.ci-remote] +dependencies = ["build", "test", "format-check", "header-check", "clippy", "audit"] \ No newline at end of file diff --git a/README.md b/README.md index 3027570..1a5dc61 100644 --- a/README.md +++ b/README.md @@ -1 +1,17 @@ -# proof-of-sql-verifier \ No newline at end of file +# Proof of SQL Verifier + +Proof of SQL Verifier is a Rust library for verifying SQL query results using zero-knowledge proofs. + +## Features + +- Verification of Dory proofs +- Integration with the proof-of-sql library + +## Installation + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +proof-of-sql-verifier = "0.1.0" +``` diff --git a/scripts/add_header_if_missing.sh b/scripts/add_header_if_missing.sh new file mode 100755 index 0000000..ac62562 --- /dev/null +++ b/scripts/add_header_if_missing.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +set -euo pipefail +shopt -s extglob + +HEADER=${1:-HEADER-APACHE2} +START_PATH=${2:-"./**/*.rs"} +CHECK_DIRTY=${CHECK_DIRTY:="false"} +DRY_RUN=${DRY_RUN:-"false"} + +N=`wc -l "${HEADER}" | awk '{print $1}'` + +DIRTY=0 + +function add_header { + local path=$1; + local header=${2:-${HEADER}}; + + local tmp=`mktemp` + + # Add \n at the top + echo "" > "${tmp}" + # Copy original + cat "${path}" >> "${tmp}" + + if [ "${DRY_RUN}" == "true" ]; then + echo "Dry run... '${path}' - '${header}' - '${tmp}'"; + return; + fi + if [ "${CHECK_DIRTY}" == "true" ]; then + return; + fi + # Mix them + cat "${header}" "${tmp}" > "${path}" +} + +for f in ${START_PATH}; do + if ! diff <(head -n "${N}" "${f}") <(cat "${HEADER}") > /dev/null ; then + echo "'${f}' Doesn't start with header from '${HEADER}': Add it"; + add_header "${f}" "${HEADER}" + DIRTY=1 + fi +done + +if [[ "${CHECK_DIRTY}" == "true" && ${DIRTY} == 1 ]]; then + exit 1; +fi \ No newline at end of file diff --git a/src/dory.rs b/src/dory.rs new file mode 100644 index 0000000..1855f54 --- /dev/null +++ b/src/dory.rs @@ -0,0 +1,47 @@ +// Copyright 2024, Horizen Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{ + verify_generic::verify_proof, DoryProof, DoryPublicInput, VerificationKey, VerifyError, +}; + +/// Verifies a Dory proof against the provided public input and verification key. +/// +/// # Arguments +/// +/// * `proof` - The Dory proof to be verified. +/// * `pubs` - The public input for the proof. +/// * `vk` - The verification key used to verify the proof. +/// +/// # Type Parameters +/// +/// * `N` - A const generic parameter, likely related to the size of the verification key. +/// +/// # Returns +/// +/// * `Result<(), VerifyError>` - Ok(()) if the proof is valid, or an error if verification fails. +pub fn verify_dory_proof( + proof: &DoryProof, + pubs: &DoryPublicInput, + vk: &VerificationKey, +) -> Result<(), VerifyError> { + verify_proof( + proof.clone().into_dory(), + pubs.expr(), + pubs.commitments(), + pubs.query_data(), + &vk.into_dory(), + ) +} diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..688448a --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,27 @@ +// Copyright 2024, Horizen Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// This module defines errors used across the verification library. +#[derive(Debug, PartialEq)] +pub enum VerifyError { + /// Provided data has invalid public inputs. + InvalidInput, + /// Provided data has invalid proof. + InvalidProofData, + /// Verify proof failed. + VerificationFailed, // Renamed for clarity + /// Provided an invalid verification key. + InvalidVerificationKey, +} diff --git a/src/inner_product.rs b/src/inner_product.rs new file mode 100644 index 0000000..8dbc4db --- /dev/null +++ b/src/inner_product.rs @@ -0,0 +1,49 @@ +// Copyright 2024, Horizen Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use proof_of_sql::{ + base::commitment::QueryCommitments, + sql::{ + ast::ProofPlan, + proof::{QueryData, VerifiableQueryResult}, + }, +}; + +use crate::{error::VerifyError, verify_generic::verify_proof}; + +pub use blitzar::proof::InnerProductProof; +pub use curve25519_dalek::RistrettoPoint; +pub use proof_of_sql::base::scalar::Curve25519Scalar; + +/// Verifies an inner product proof against the provided expression, commitments, and query data. +/// +/// # Arguments +/// +/// * `proof` - The inner product proof to be verified, wrapped in a VerifiableQueryResult. +/// * `expr` - The proof plan expression. +/// * `commitments` - The query commitments. +/// * `query_data` - The query data. +/// +/// # Returns +/// +/// * `Result<(), VerifyError>` - Ok(()) if the proof is valid, or an error if verification fails. +pub fn verify_inner_product_proof( + proof: VerifiableQueryResult, + expr: &ProofPlan, + commitments: &QueryCommitments, + query_data: &QueryData, +) -> Result<(), VerifyError> { + verify_proof(proof, expr, commitments, query_data, &()) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..0fe699c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,35 @@ +// Copyright 2024, Horizen Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod errors; +mod proof; +mod pubs; +mod verification_key; +mod verify_generic; + +pub mod dory; +pub use dory::*; + +pub use errors::*; +pub use proof::*; +pub use pubs::*; +pub use verification_key::*; +pub use verify_generic::*; + +#[cfg(feature = "inner-product")] +pub mod inner_product; + +#[cfg(feature = "inner-product")] +pub use inner_product::*; diff --git a/src/proof.rs b/src/proof.rs new file mode 100644 index 0000000..9a4cd3d --- /dev/null +++ b/src/proof.rs @@ -0,0 +1,84 @@ +// Copyright 2024, Horizen Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use proof_of_sql::proof_primitive::dory::DoryEvaluationProof; +use proof_of_sql::sql::proof::VerifiableQueryResult; + +use crate::VerifyError; + +/// Represents a Dory proof. +/// +/// `DoryProof` is a wrapper around a `VerifiableQueryResult`. +/// It provides methods for creating, serializing, and deserializing Dory proofs. +/// +/// # Fields +/// +/// * `proof` - A `VerifiableQueryResult` containing the actual proof data. +#[derive(Clone)] +pub struct DoryProof { + proof: VerifiableQueryResult, +} + +impl TryFrom<&[u8]> for DoryProof { + type Error = VerifyError; + + /// Attempts to create a DoryProof from a byte slice. + /// + /// # Arguments + /// + /// * `value` - The byte slice containing the serialized proof. + /// + /// # Returns + /// + /// * `Result` - A DoryProof if deserialization succeeds, or a VerifyError if it fails. + fn try_from(value: &[u8]) -> Result { + let proof = bincode::deserialize(value).map_err(|_| VerifyError::InvalidProofData)?; + + Ok(Self::new(proof)) + } +} + +impl DoryProof { + /// Creates a new DoryProof. + /// + /// # Arguments + /// + /// * `proof` - A VerifiableQueryResult containing a DoryEvaluationProof. + /// + /// # Returns + /// + /// * `Self` - A new DoryProof instance. + pub fn new(proof: VerifiableQueryResult) -> Self { + Self { proof } + } + + /// Converts the DoryProof into a byte vector. + /// + /// # Returns + /// + /// * `Vec` - The serialized proof as a byte vector. + pub fn into_bytes(self) -> Vec { + bincode::serialize(&self.proof).unwrap() + } + + /// Converts the DoryProof into a VerifiableQueryResult. + /// + /// # Returns + /// + /// * `VerifiableQueryResult` - The proof data. + pub fn into_dory(self) -> VerifiableQueryResult { + self.proof + } +} diff --git a/src/pubs.rs b/src/pubs.rs new file mode 100644 index 0000000..6fc96f2 --- /dev/null +++ b/src/pubs.rs @@ -0,0 +1,75 @@ +// Copyright 2024, Horizen Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use proof_of_sql::{ + base::commitment::QueryCommitments, + proof_primitive::dory::{DoryCommitment, DoryScalar}, + sql::{ast::ProofPlan, parse::QueryExpr, proof::QueryData}, +}; + +/// Represents the public input for a Dory proof. +/// +/// This structure encapsulates the necessary public information required +/// for verifying a Dory proof, including the proof expression, commitments, +/// and query data. +/// +/// # Type Parameters +/// +/// * `'a` - The lifetime of the referenced `ProofPlan`. +pub struct DoryPublicInput<'a> { + expr: &'a ProofPlan, + commitments: QueryCommitments, + query_data: QueryData, +} + +impl<'a> DoryPublicInput<'a> { + /// Creates a new `DoryPublicInput` instance. + /// + /// # Arguments + /// + /// * `query_expr` - A reference to the query expression. + /// * `commitments` - The query commitments. + /// * `query_data` - The query data. + /// + /// # Returns + /// + /// A new `DoryPublicInput` instance. + pub fn new( + query_expr: &'a QueryExpr, + commitments: QueryCommitments, + query_data: QueryData, + ) -> Self { + Self { + expr: query_expr.proof_expr(), + commitments, + query_data, + } + } + + /// Returns a reference to the proof expression. + pub fn expr(&self) -> &ProofPlan { + self.expr + } + + /// Returns a reference to the query commitments. + pub fn commitments(&self) -> &QueryCommitments { + &self.commitments + } + + /// Returns a reference to the query data. + pub fn query_data(&self) -> &QueryData { + &self.query_data + } +} diff --git a/src/verification_key.rs b/src/verification_key.rs new file mode 100644 index 0000000..7f1e173 --- /dev/null +++ b/src/verification_key.rs @@ -0,0 +1,131 @@ +// Copyright 2024, Horizen Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use ark_serialize::CanonicalDeserialize; +use proof_of_sql::proof_primitive::dory::{ + DoryVerifierPublicSetup, PublicParameters, VerifierSetup, +}; + +use crate::VerifyError; + +/// Represents a verification key for Dory proofs. +/// +/// This structure wraps a `VerifierSetup` and provides methods for +/// creating, deserializing, and converting the verification key. +/// +/// # Type Parameters +/// +/// * `N` - A const generic parameter representing the size of the verification key. +pub struct VerificationKey(VerifierSetup); + +impl TryFrom<&[u8]> for VerificationKey { + type Error = VerifyError; + + /// Attempts to create a VerificationKey from a byte slice. + /// + /// # Arguments + /// + /// * `value` - The byte slice containing the serialized verification key. + /// + /// # Returns + /// + /// * `Result` - A VerificationKey if deserialization succeeds, or a VerifyError if it fails. + fn try_from(value: &[u8]) -> Result { + let setup = VerifierSetup::deserialize_compressed(value) + .map_err(|_| VerifyError::InvalidVerificationKey)?; + + // read last usize from the buffer as max_nu is the last field in the struct, and check if it matches N + // max_nu is not accessible from the VerifierSetup struct, so we need to check it from the buffer + let max_nu = slice_to_usize(&value[value.len() - std::mem::size_of::()..]); + if max_nu != N { + return Err(VerifyError::InvalidVerificationKey); + } + + Ok(Self(setup)) + } +} + +impl VerificationKey { + /// Creates a new VerificationKey from PublicParameters. + /// + /// # Arguments + /// + /// * `params` - A reference to PublicParameters. + /// + /// # Returns + /// + /// A new VerificationKey instance. + pub fn new(params: &PublicParameters) -> Self { + Self(VerifierSetup::from(params)) + } + + /// Converts the VerificationKey into a DoryVerifierPublicSetup. + /// + /// # Returns + /// + /// A DoryVerifierPublicSetup instance. + pub fn into_dory(&self) -> DoryVerifierPublicSetup<'_> { + DoryVerifierPublicSetup::new(&self.0, N) + } +} + +/// Converts a byte slice to a usize. +/// +/// # Arguments +/// +/// * `slice` - The byte slice to convert. +/// +/// # Returns +/// +/// The usize value represented by the byte slice. +fn slice_to_usize(slice: &[u8]) -> usize { + let mut array = [0u8; std::mem::size_of::()]; + let len = slice.len().min(std::mem::size_of::()); + array[..len].copy_from_slice(&slice[..len]); + + usize::from_le_bytes(array) +} + +#[cfg(test)] +mod test { + use ark_serialize::CanonicalSerialize; + use proof_of_sql::proof_primitive::dory::{test_rng, PublicParameters}; + + use super::*; + + #[test] + fn test_verification_key() { + let public_parameters = PublicParameters::rand(4, &mut test_rng()); + let vs: VerifierSetup = VerifierSetup::from(&public_parameters); + let mut writer = Vec::new(); + vs.serialize_compressed(&mut writer).unwrap(); + + let verification_key = VerificationKey::<4>::try_from(writer.as_ref()).unwrap(); + let dory_key = verification_key.into_dory(); + + assert_eq!(dory_key.verifier_setup(), &vs); + } + + #[test] + fn test_verification_key_short_buffer() { + let public_parameters = PublicParameters::rand(4, &mut test_rng()); + let vs: VerifierSetup = VerifierSetup::from(&public_parameters); + let mut writer = Vec::new(); + vs.serialize_compressed(&mut writer).unwrap(); + + let verification_key = VerificationKey::<4>::try_from(&writer[..writer.len() - 1]); + assert!(verification_key.is_err()); + } +} diff --git a/src/verify_generic.rs b/src/verify_generic.rs new file mode 100644 index 0000000..8367b8a --- /dev/null +++ b/src/verify_generic.rs @@ -0,0 +1,78 @@ +// Copyright 2024, Horizen Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use proof_of_sql::base::commitment::CommitmentEvaluationProof; +use proof_of_sql::sql::proof::ProofExpr; +use proof_of_sql::{ + base::commitment::QueryCommitments, + sql::{ + ast::ProofPlan, + proof::{QueryData, VerifiableQueryResult}, + }, +}; + +use crate::VerifyError; + +/// Verifies a generic proof against the provided expression, commitments, and query data. +/// +/// # Type Parameters +/// +/// * `CP` - A type that implements `CommitmentEvaluationProof`. +/// +/// # Arguments +/// +/// * `proof` - The proof to be verified, wrapped in a `VerifiableQueryResult`. +/// * `expr` - The proof plan expression. +/// * `commitments` - The query commitments. +/// * `query_data` - The query data. +/// * `setup` - The verifier's public setup. +/// +/// # Returns +/// +/// * `Result<(), VerifyError>` - Ok(()) if the proof is valid, or an error if verification fails. +pub fn verify_proof( + proof: VerifiableQueryResult, + expr: &ProofPlan, + commitments: &QueryCommitments, + query_data: &QueryData, + setup: &CP::VerifierPublicSetup<'_>, +) -> Result<(), VerifyError> { + // Check that the columns in the proof match the columns in the commitments + for column in expr.get_column_references() { + if let Some(commitment) = commitments.get(&column.table_ref()) { + if let Some(metadata) = commitment + .column_commitments() + .get_metadata(&column.column_id()) + { + if metadata.column_type() != column.column_type() { + return Err(VerifyError::InvalidInput); + } + } + } else { + return Err(VerifyError::InvalidInput); + } + } + + let result = proof + .verify(expr, commitments, setup) + .map_err(|_| VerifyError::VerificationFailed)?; + + if result.table != query_data.table || result.verification_hash != query_data.verification_hash + { + Err(VerifyError::VerificationFailed) + } else { + Ok(()) + } +} diff --git a/tests/integration.rs b/tests/integration.rs new file mode 100644 index 0000000..628c4d5 --- /dev/null +++ b/tests/integration.rs @@ -0,0 +1,336 @@ +// Copyright 2024, Horizen Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use proof_of_sql::base::{commitment::QueryCommitments, database::CommitmentAccessor}; +pub use proof_of_sql::{ + base::{ + commitment::{Commitment, CommitmentEvaluationProof, QueryCommitmentsExt}, + database::{owned_table_utility::*, OwnedTableTestAccessor, SchemaAccessor, TestAccessor}, + }, + sql::{parse::QueryExpr, proof::ProofExpr, proof::VerifiableQueryResult}, +}; + +// Helper functions for setting up test data and queries + +/// Computes query commitments for a given query expression and accessor. +fn compute_query_commitments( + query_expr: &QueryExpr, + accessor: &(impl CommitmentAccessor + SchemaAccessor), +) -> QueryCommitments { + let columns = query_expr.proof_expr().get_column_references(); + QueryCommitments::from_accessor_with_max_bounds(columns, accessor) +} + +/// Builds a test accessor with sample data. +fn build_accessor( + setup: ::ProverPublicSetup<'_>, +) -> OwnedTableTestAccessor { + let mut accessor = OwnedTableTestAccessor::::new_empty_with_setup(setup); + accessor.add_table( + "sxt.table".parse().unwrap(), + owned_table([ + bigint("a", [1, 2, 3, 2]), + varchar("b", ["hi", "hello", "there", "world"]), + ]), + 0, + ); + accessor +} + +/// Builds a test accessor with altered sample data. +fn build_altered_accessor( + setup: ::ProverPublicSetup<'_>, +) -> OwnedTableTestAccessor { + let mut accessor = OwnedTableTestAccessor::::new_empty_with_setup(setup); + accessor.add_table( + "sxt.table".parse().unwrap(), + owned_table([ + bigint("a", [1, 2, 3, 2]), + varchar("b", ["hi", "hello", "there", "zkVerify"]), + ]), + 0, + ); + accessor +} + +/// Builds a test accessor with different table and column names. +fn build_alien_accessor( + setup: ::ProverPublicSetup<'_>, +) -> OwnedTableTestAccessor { + let mut accessor = OwnedTableTestAccessor::::new_empty_with_setup(setup); + accessor.add_table( + "sxt.table2".parse().unwrap(), + owned_table([ + bigint("c", [1, 2, 3, 2]), + varchar("d", ["hi", "hello", "there", "world"]), + ]), + 0, + ); + accessor +} + +/// Builds a sample query for testing. +fn build_query(accessor: &impl SchemaAccessor) -> QueryExpr { + QueryExpr::try_new( + "SELECT b FROM table WHERE a = 2".parse().unwrap(), + "sxt".parse().unwrap(), + accessor, + ) + .unwrap() +} + +/// Builds a sample query for the "alien" accessor. +fn build_alien_query(accessor: &impl SchemaAccessor) -> QueryExpr { + QueryExpr::try_new( + "SELECT d FROM table2 WHERE c = 2".parse().unwrap(), + "sxt".parse().unwrap(), + accessor, + ) + .unwrap() +} + +/// Builds a query for a non-existent record. +fn build_query_non_existant_record(accessor: &impl SchemaAccessor) -> QueryExpr { + QueryExpr::try_new( + "SELECT b FROM table WHERE a = 4".parse().unwrap(), + "sxt".parse().unwrap(), + accessor, + ) + .unwrap() +} + +#[cfg(feature = "inner-product")] +mod inner_product { + use super::*; + + use blitzar::{self, proof::InnerProductProof}; + + /// Tests the generation and verification of an inner product proof. + #[test] + fn generate_and_verify_proof() { + blitzar::compute::init_backend(); + + let prover_setup = (); + let verifier_setup = (); + + let accessor: OwnedTableTestAccessor = build_accessor(prover_setup); + let query = build_query(&accessor); + + let proof = VerifiableQueryResult::::new( + query.proof_expr(), + &accessor, + &prover_setup, + ); + + let query_data = proof + .verify(query.proof_expr(), &accessor, &verifier_setup) + .unwrap(); + let query_commitments = compute_query_commitments(&query, &accessor); + + let result = proof_of_sql_verifier::verify_inner_product_proof( + proof, + query.proof_expr(), + &query_commitments, + &query_data, + ); + + assert!(result.is_ok()); + } +} + +mod dory { + use super::*; + + use proof_of_sql::proof_primitive::dory::{ + test_rng, DoryEvaluationProof, DoryProverPublicSetup, ProverSetup, PublicParameters, + }; + + use proof_of_sql::base::commitment::QueryCommitments; + use proof_of_sql_verifier::{DoryProof, DoryPublicInput, VerificationKey}; + + /// Tests the generation and verification of a Dory proof. + #[test] + fn generate_and_verify_proof() { + // Initialize setup + let public_parameters = PublicParameters::rand(4, &mut test_rng()); + let ps = ProverSetup::from(&public_parameters); + let prover_setup = DoryProverPublicSetup::new(&ps, 4); + + // Build table accessor and query + let accessor = build_accessor::(prover_setup); + let query = build_query(&accessor); + + // Generate proof + let proof = VerifiableQueryResult::::new( + query.proof_expr(), + &accessor, + &prover_setup, + ); + + // Get query data and commitments + let vk = VerificationKey::<4>::new(&public_parameters); + let query_data = proof + .verify(query.proof_expr(), &accessor, &vk.into_dory()) + .unwrap(); + let query_commitments = compute_query_commitments(&query, &accessor); + + // Verify proof + let proof = DoryProof::new(proof); + let pubs = DoryPublicInput::new(&query, query_commitments, query_data); + let result = proof_of_sql_verifier::verify_dory_proof(&proof, &pubs, &vk); + + assert!(result.is_ok()); + } + + /// Tests the generation and verification of a Dory proof for a non-existent record. + #[test] + fn generate_and_verify_proof_for_non_existant_record() { + // Initialize setup + let public_parameters = PublicParameters::rand(4, &mut test_rng()); + let ps = ProverSetup::from(&public_parameters); + let prover_setup = DoryProverPublicSetup::new(&ps, 4); + + // Build table accessor and query + let accessor = build_accessor::(prover_setup); + let non_existant_query = build_query_non_existant_record(&accessor); + + let proof = VerifiableQueryResult::::new( + non_existant_query.proof_expr(), + &accessor, + &prover_setup, + ); + + let vk = VerificationKey::<4>::new(&public_parameters); + + let query_data = proof + .verify(non_existant_query.proof_expr(), &accessor, &vk.into_dory()) + .unwrap(); + let query_commitments = compute_query_commitments(&non_existant_query, &accessor); + + let dory_proof = DoryProof::new(proof); + let pubs = DoryPublicInput::new(&non_existant_query, query_commitments, query_data); + let result = proof_of_sql_verifier::verify_dory_proof(&dory_proof, &pubs, &vk); + + assert!(result.is_ok()); + } + + /// Tests that verification fails when commitments are missing. + #[test] + fn generate_and_verify_proof_without_commitments() { + // Initialize setup + let public_parameters = PublicParameters::rand(4, &mut test_rng()); + let ps = ProverSetup::from(&public_parameters); + let prover_setup = DoryProverPublicSetup::new(&ps, 4); + + // Build table accessor and query + let accessor = build_accessor::(prover_setup); + let query = build_query(&accessor); + + // Generate proof + let proof = VerifiableQueryResult::::new( + query.proof_expr(), + &accessor, + &prover_setup, + ); + + // Get query data and commitments + let vk = VerificationKey::<4>::new(&public_parameters); + let query_data = proof + .verify(query.proof_expr(), &accessor, &vk.into_dory()) + .unwrap(); + let no_commitments = QueryCommitments::new(); + + let proof = DoryProof::new(proof); + let pubs = DoryPublicInput::new(&query, no_commitments, query_data); + let result = proof_of_sql_verifier::verify_dory_proof(&proof, &pubs, &vk); + + assert!(result.is_err()); + } + + /// Tests that verification fails when the underlying data has been altered. + #[test] + fn generate_and_verify_proof_for_altered_data() { + // Initialize setup + let public_parameters = PublicParameters::rand(4, &mut test_rng()); + let ps = ProverSetup::from(&public_parameters); + let prover_setup = DoryProverPublicSetup::new(&ps, 4); + + // Build table accessor and query + let accessor = build_accessor::(prover_setup); + let query = build_query(&accessor); + + // Generate proof + let proof = VerifiableQueryResult::::new( + query.proof_expr(), + &accessor, + &prover_setup, + ); + + // Get query data and commitments + let vk = VerificationKey::<4>::new(&public_parameters); + let query_data = proof + .verify(query.proof_expr(), &accessor, &vk.into_dory()) + .unwrap(); + + // Alter the data + let altered_accessor: OwnedTableTestAccessor = + build_altered_accessor(prover_setup); + let altered_query_commitments = compute_query_commitments(&query, &altered_accessor); + + // Verify proof + let proof = DoryProof::new(proof); + let pubs = DoryPublicInput::new(&query, altered_query_commitments, query_data); + let result = proof_of_sql_verifier::verify_dory_proof(&proof, &pubs, &vk); + + assert!(result.is_err()); + } + + /// Tests that verification fails when using commitments from a different accessor. + #[test] + fn generate_and_verify_proof_from_alien_accessor() { + // Initialize setup + let public_parameters = PublicParameters::rand(4, &mut test_rng()); + let ps = ProverSetup::from(&public_parameters); + let prover_setup = DoryProverPublicSetup::new(&ps, 4); + + // Build table accessors and queries + let accessor = build_accessor::(prover_setup); + let alien_accessor = build_alien_accessor::(prover_setup); + let query = build_query(&accessor); + let alient_query = build_alien_query(&alien_accessor); + + // Generate proof for original accessor and query + let proof = VerifiableQueryResult::::new( + query.proof_expr(), + &accessor, + &prover_setup, + ); + + // Get the result + let vk = VerificationKey::<4>::new(&public_parameters); + let query_data = proof + .verify(query.proof_expr(), &accessor, &vk.into_dory()) + .unwrap(); + + // Compute query commitments for alien accessor + let query_commitments = compute_query_commitments(&alient_query, &alien_accessor); + + let proof = DoryProof::new(proof); + let pubs = DoryPublicInput::new(&query, query_commitments, query_data); + let result = proof_of_sql_verifier::verify_dory_proof(&proof, &pubs, &vk); + + assert!(result.is_err()); + } +}