proof_of_sql/sql/proof/
verifiable_query_result.rs

1use super::{ProofPlan, QueryData, QueryProof, QueryResult};
2use crate::{
3    base::{
4        commitment::CommitmentEvaluationProof,
5        database::{CommitmentAccessor, DataAccessor, OwnedTable},
6    },
7    utils::log,
8};
9use serde::{Deserialize, Serialize};
10
11/// The result of an sql query along with a proof that the query is valid. The
12/// result and proof can be verified using commitments to database columns.
13///
14/// Note: the query result is stored in an intermediate form rather than the final form
15/// the end-user sees. The final form is obtained after verification. Using an
16/// intermediate form allows us to handle overflow and certain cases where the final
17/// result might use floating point numbers (e.g. `SELECT STDDEV(A) FROM T WHERE B = 0`).
18///
19/// Below we demonstrate typical usage of [`VerifiableQueryResult`] with pseudo-code.
20///
21/// Here we assume that a verifier only has access to the commitments of database columns. To
22/// process a query, the verifier forwards the query to an untrusted
23/// prover. The prover has full access to the database and constructs a [`VerifiableQueryResult`] that
24/// it sends back to the verifier. The verifier checks that the result is valid using its
25/// commitments, and constructs the finalized form of the query result.
26///
27/// ```ignore
28/// prover_process_query(database_accessor) {
29///       query <- receive_query_from_verifier()
30///
31///       verifiable_result <- VerifiableQueryResult::new(query, database_accessor)
32///             // When we construct VerifiableQueryResult from a query expression, we compute
33///             // both the result of the query in intermediate form and the proof of the result
34///             // at the same time.
35///
36///       send_to_verifier(verifiable_result)
37/// }
38///
39/// verifier_process_query(query, commitment_accessor) {
40///    verifiable_result <- send_query_to_prover(query)
41///
42///    verify_result <- verifiable_result.verify(query, commitment_accessor)
43///    if verify_result.is_error() {
44///         // The prover did something wrong. Perhaps the prover tried to tamper with the query
45///         // result or maybe its version of the database was out-of-sync with the verifier's
46///         // version.
47///         do_verification_error()
48///    }
49///
50///    query_result <- verify_result.query_result()
51///    if query_result.is_error() {
52///         // The prover processed the query correctly, but the query resulted in an error.
53///         // For example, perhaps the query added two 64-bit integer columns together that
54///         // resulted in an overflow.
55///         do_query_error()
56///    }
57///
58///    do_query_success(query_result)
59///         // The prover correctly processed a query and the query succeeded. Now, we can
60///         // proceed to use the result.
61/// }
62/// ```
63///
64/// Note: Because the class is deserialized from untrusted data, it
65/// cannot maintain any invariant on its data members; hence, they are
66/// all public so as to allow for easy manipulation for testing.
67#[derive(Clone, Serialize, Deserialize)]
68pub struct VerifiableQueryResult<CP: CommitmentEvaluationProof> {
69    /// The result of the query in intermediate form.
70    pub result: OwnedTable<CP::Scalar>,
71    /// The proof that the query result is valid.
72    pub proof: QueryProof<CP>,
73}
74
75impl<CP: CommitmentEvaluationProof> VerifiableQueryResult<CP> {
76    /// Form a `VerifiableQueryResult` from a query expression.
77    ///
78    /// This function both computes the result of a query and constructs a proof of the results
79    /// validity.
80    #[tracing::instrument(name = "VerifiableQueryResult::new", level = "info", skip_all)]
81    pub fn new(
82        expr: &(impl ProofPlan + Serialize),
83        accessor: &impl DataAccessor<CP::Scalar>,
84        setup: &CP::ProverPublicSetup<'_>,
85    ) -> Self {
86        log::log_memory_usage("Start");
87        let (proof, res) = QueryProof::new(expr, accessor, setup);
88        log::log_memory_usage("End");
89        Self { result: res, proof }
90    }
91
92    /// Verify a `VerifiableQueryResult`. Upon success, this function returns the finalized form of
93    /// the query result.
94    ///
95    /// Note: a verified result can still respresent an error (e.g. overflow), but it is a verified
96    /// error.
97    ///
98    /// Note: This does NOT transform the result!
99    #[tracing::instrument(name = "VerifiableQueryResult::verify", level = "info", skip_all)]
100    pub fn verify(
101        self,
102        expr: &(impl ProofPlan + Serialize),
103        accessor: &impl CommitmentAccessor<CP::Commitment>,
104        setup: &CP::VerifierPublicSetup<'_>,
105    ) -> QueryResult<CP::Scalar> {
106        log::log_memory_usage("Start");
107        let QueryData {
108            table,
109            verification_hash,
110        } = self.proof.verify(expr, accessor, self.result, setup)?;
111        Ok(QueryData {
112            table: table.try_coerce_with_fields(expr.get_column_result_fields())?,
113            verification_hash,
114        })
115    }
116}