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}