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