proof_of_sql/sql/proof/verifiable_query_result.rs
1use super::{ProofPlan, QueryData, QueryProof, QueryResult};
2use crate::{
3 base::{
4 commitment::CommitmentEvaluationProof,
5 database::{
6 ColumnField, ColumnType, CommitmentAccessor, DataAccessor, OwnedColumn, OwnedTable,
7 },
8 proof::ProofError,
9 scalar::Scalar,
10 },
11 utils::log,
12};
13use alloc::vec;
14use serde::{Deserialize, Serialize};
15
16/// The result of an sql query along with a proof that the query is valid. The
17/// result and proof can be verified using commitments to database columns.
18///
19/// Note: the query result is stored in an intermediate form rather than the final form
20/// the end-user sees. The final form is obtained after verification. Using an
21/// intermediate form allows us to handle overflow and certain cases where the final
22/// result might use floating point numbers (e.g. `SELECT STDDEV(A) FROM T WHERE B = 0`).
23///
24/// Below we demonstrate typical usage of [`VerifiableQueryResult`] with pseudo-code.
25///
26/// Here we assume that a verifier only has access to the commitments of database columns. To
27/// process a query, the verifier forwards the query to an untrusted
28/// prover. The prover has full access to the database and constructs a [`VerifiableQueryResult`] that
29/// it sends back to the verifier. The verifier checks that the result is valid using its
30/// commitments, and constructs the finalized form of the query result.
31///
32/// ```ignore
33/// prover_process_query(database_accessor) {
34/// query <- receive_query_from_verifier()
35///
36/// verifiable_result <- VerifiableQueryResult::new(query, database_accessor)
37/// // When we construct VerifiableQueryResult from a query expression, we compute
38/// // both the result of the query in intermediate form and the proof of the result
39/// // at the same time.
40///
41/// send_to_verifier(verifiable_result)
42/// }
43///
44/// verifier_process_query(query, commitment_accessor) {
45/// verifiable_result <- send_query_to_prover(query)
46///
47/// verify_result <- verifiable_result.verify(query, commitment_accessor)
48/// if verify_result.is_error() {
49/// // The prover did something wrong. Perhaps the prover tried to tamper with the query
50/// // result or maybe its version of the database was out-of-sync with the verifier's
51/// // version.
52/// do_verification_error()
53/// }
54///
55/// query_result <- verify_result.query_result()
56/// if query_result.is_error() {
57/// // The prover processed the query correctly, but the query resulted in an error.
58/// // For example, perhaps the query added two 64-bit integer columns together that
59/// // resulted in an overflow.
60/// do_query_error()
61/// }
62///
63/// do_query_success(query_result)
64/// // The prover correctly processed a query and the query succeeded. Now, we can
65/// // proceed to use the result.
66/// }
67/// ```
68///
69/// Note: Because the class is deserialized from untrusted data, it
70/// cannot maintain any invariant on its data members; hence, they are
71/// all public so as to allow for easy manipulation for testing.
72#[derive(Default, Clone, Serialize, Deserialize)]
73pub struct VerifiableQueryResult<CP: CommitmentEvaluationProof> {
74 /// The result of the query in intermediate form.
75 pub(super) result: Option<OwnedTable<CP::Scalar>>,
76 /// The proof that the query result is valid.
77 pub(super) proof: Option<QueryProof<CP>>,
78}
79
80impl<CP: CommitmentEvaluationProof> VerifiableQueryResult<CP> {
81 /// Form a `VerifiableQueryResult` from a query expression.
82 ///
83 /// This function both computes the result of a query and constructs a proof of the results
84 /// validity.
85 #[tracing::instrument(name = "VerifiableQueryResult::new", level = "info", skip_all)]
86 pub fn new(
87 expr: &(impl ProofPlan + Serialize),
88 accessor: &impl DataAccessor<CP::Scalar>,
89 setup: &CP::ProverPublicSetup<'_>,
90 ) -> Self {
91 log::log_memory_usage("Start");
92
93 // a query must have at least one result column; if not, it should
94 // have been rejected at the parsing stage.
95
96 // handle the empty case
97 let table_refs = expr.get_table_references();
98 if table_refs
99 .into_iter()
100 .all(|table_ref| accessor.get_length(&table_ref) == 0)
101 {
102 return VerifiableQueryResult {
103 result: None,
104 proof: None,
105 };
106 }
107
108 let (proof, res) = QueryProof::new(expr, accessor, setup);
109
110 log::log_memory_usage("End");
111
112 Self {
113 result: Some(res),
114 proof: Some(proof),
115 }
116 }
117
118 /// Verify a `VerifiableQueryResult`. Upon success, this function returns the finalized form of
119 /// the query result.
120 ///
121 /// Note: a verified result can still respresent an error (e.g. overflow), but it is a verified
122 /// error.
123 ///
124 /// Note: This does NOT transform the result!
125 #[tracing::instrument(name = "VerifiableQueryResult::verify", level = "info", skip_all)]
126 pub fn verify(
127 self,
128 expr: &(impl ProofPlan + Serialize),
129 accessor: &impl CommitmentAccessor<CP::Commitment>,
130 setup: &CP::VerifierPublicSetup<'_>,
131 ) -> QueryResult<CP::Scalar> {
132 log::log_memory_usage("Start");
133
134 match (self.result, self.proof) {
135 (Some(result), Some(proof)) => {
136 let QueryData {
137 table,
138 verification_hash,
139 } = proof.verify(expr, accessor, result, setup)?;
140 Ok(QueryData {
141 table: table.try_coerce_with_fields(expr.get_column_result_fields())?,
142 verification_hash,
143 })
144 }
145 (None, None)
146 if expr
147 .get_table_references()
148 .into_iter()
149 .all(|table_ref| accessor.get_length(&table_ref) == 0) =>
150 {
151 let result_fields = expr.get_column_result_fields();
152 make_empty_query_result(&result_fields)
153 }
154 _ => Err(ProofError::VerificationError {
155 error: "Proof does not match result: at least one is missing",
156 })?,
157 }
158 }
159}
160
161fn make_empty_query_result<S: Scalar>(result_fields: &[ColumnField]) -> QueryResult<S> {
162 let table = OwnedTable::try_new(
163 result_fields
164 .iter()
165 .map(|field| {
166 (
167 field.name(),
168 match field.data_type() {
169 ColumnType::Boolean => OwnedColumn::Boolean(vec![]),
170 ColumnType::Uint8 => OwnedColumn::Uint8(vec![]),
171 ColumnType::TinyInt => OwnedColumn::TinyInt(vec![]),
172 ColumnType::SmallInt => OwnedColumn::SmallInt(vec![]),
173 ColumnType::Int => OwnedColumn::Int(vec![]),
174 ColumnType::BigInt => OwnedColumn::BigInt(vec![]),
175 ColumnType::Int128 => OwnedColumn::Int128(vec![]),
176 ColumnType::Decimal75(precision, scale) => {
177 OwnedColumn::Decimal75(precision, scale, vec![])
178 }
179 ColumnType::Scalar => OwnedColumn::Scalar(vec![]),
180 ColumnType::VarChar => OwnedColumn::VarChar(vec![]),
181 ColumnType::VarBinary => OwnedColumn::VarBinary(vec![]),
182 ColumnType::TimestampTZ(tu, tz) => OwnedColumn::TimestampTZ(tu, tz, vec![]),
183 },
184 )
185 })
186 .collect(),
187 )?;
188 Ok(QueryData {
189 table,
190 verification_hash: Default::default(),
191 })
192}