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}