Skip to main content

hyphae_engine/proof/
model.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use std::{io, time::Duration};
4
5use hyphae_query::{ExecutionLimits, Query, QueryError, QueryResult, Record};
6use hyphae_storage::{SnapshotError, SnapshotInfo, SnapshotReadLimits};
7use thiserror::Error;
8
9use crate::DocumentError;
10
11/// Version of the canonical result-proof envelope.
12pub const RESULT_PROOF_FORMAT_VERSION: u16 = 1;
13
14/// Hard maximum canonical proof file length.
15pub const MAX_RESULT_PROOF_BYTES: u64 = 64 * 1024 * 1024;
16
17/// Snapshot and log checkpoint identity trusted by one result proof.
18#[derive(Clone, Debug, Eq, PartialEq)]
19pub struct ProofAnchor {
20    /// Materialized commit sequence captured by the snapshot.
21    pub checkpoint_sequence: u64,
22    /// Commit digest captured by the snapshot, absent only for empty history.
23    pub checkpoint_digest: Option<[u8; 32]>,
24    /// Canonical logical snapshot digest.
25    pub snapshot_digest: [u8; 32],
26}
27
28impl ProofAnchor {
29    /// Creates an anchor from already verified snapshot metadata.
30    pub fn from_snapshot(snapshot: &SnapshotInfo) -> Self {
31        Self {
32            checkpoint_sequence: snapshot.checkpoint_sequence,
33            checkpoint_digest: snapshot.checkpoint_digest,
34            snapshot_digest: snapshot.snapshot_digest,
35        }
36    }
37
38    /// Computes the caller-pinnable domain-separated anchor digest.
39    pub fn digest(&self) -> [u8; 32] {
40        let mut hasher = blake3::Hasher::new();
41        hasher.update(b"hyphae-proof-anchor-v1");
42        hasher.update(&self.checkpoint_sequence.to_le_bytes());
43        hasher.update(&self.checkpoint_digest.unwrap_or([0; 32]));
44        hasher.update(&self.snapshot_digest);
45        *hasher.finalize().as_bytes()
46    }
47}
48
49/// Durable operation embedded in a result proof.
50#[derive(Clone, Debug, Eq, PartialEq)]
51pub enum ProvenOperation {
52    /// Exact binary-key lookup.
53    Get {
54        /// Requested nonempty key.
55        key: Vec<u8>,
56    },
57    /// Complete deterministic structured query.
58    Query(Query),
59}
60
61/// Complete logical result embedded in a result proof.
62#[derive(Clone, Debug, Eq, PartialEq)]
63pub enum ProvenResult {
64    /// Exact lookup result, including verifiable absence.
65    Get(Option<Record>),
66    /// Structured query result including cursor and aggregation.
67    Query(QueryResult),
68}
69
70/// Canonical result proof with an embedded request and result.
71#[derive(Clone, Debug, Eq, PartialEq)]
72pub struct ResultProof {
73    pub(crate) anchor: ProofAnchor,
74    pub(crate) operation: ProvenOperation,
75    pub(crate) result: ProvenResult,
76    pub(crate) proof_digest: [u8; 32],
77}
78
79/// Newly created proof and the local canonical snapshot witness it references.
80#[derive(Clone, Debug, Eq, PartialEq)]
81pub struct ResultProofArtifact {
82    /// Portable canonical result proof.
83    pub proof: ResultProof,
84    /// Verified local snapshot witness metadata and path.
85    pub snapshot: SnapshotInfo,
86}
87
88impl ResultProof {
89    /// Returns the exact snapshot/log anchor.
90    pub fn anchor(&self) -> &ProofAnchor {
91        &self.anchor
92    }
93
94    /// Returns the caller-pinnable anchor digest.
95    pub fn anchor_digest(&self) -> [u8; 32] {
96        self.anchor.digest()
97    }
98
99    /// Returns the operation whose result is proven.
100    pub fn operation(&self) -> &ProvenOperation {
101        &self.operation
102    }
103
104    /// Returns the complete proven result.
105    pub fn result(&self) -> &ProvenResult {
106        &self.result
107    }
108
109    /// Returns the digest of the complete canonical proof bytes.
110    pub fn proof_digest(&self) -> [u8; 32] {
111        self.proof_digest
112    }
113}
114
115/// Resource limits for complete offline result verification.
116#[derive(Clone, Debug, Eq, PartialEq)]
117pub struct VerificationLimits {
118    /// Maximum proof file bytes accepted before allocation.
119    pub proof_bytes: u64,
120    /// Limits for loading the canonical snapshot witness.
121    pub snapshot: SnapshotReadLimits,
122    /// Reference structured-query limits used during reexecution.
123    pub query: ExecutionLimits,
124    /// End-to-end cooperative verification deadline.
125    pub timeout: Duration,
126}
127
128impl Default for VerificationLimits {
129    fn default() -> Self {
130        Self {
131            proof_bytes: MAX_RESULT_PROOF_BYTES,
132            snapshot: SnapshotReadLimits::default(),
133            query: ExecutionLimits::default(),
134            timeout: Duration::from_secs(60),
135        }
136    }
137}
138
139/// Successful offline verification evidence.
140#[derive(Clone, Debug, Eq, PartialEq)]
141pub struct VerificationReport {
142    /// Trusted anchor accepted by the verifier.
143    pub anchor: ProofAnchor,
144    /// Caller-pinnable anchor digest that matched expectation.
145    pub anchor_digest: [u8; 32],
146    /// Digest of the verified canonical proof file.
147    pub proof_digest: [u8; 32],
148    /// Complete reexecuted and verified result.
149    pub result: ProvenResult,
150}
151
152/// Failure while encoding, reading, or verifying a result proof.
153#[derive(Debug, Error)]
154pub enum ProofError {
155    /// Proof file I/O failed.
156    #[error(transparent)]
157    Io(#[from] io::Error),
158
159    /// Canonical structured document verification failed.
160    #[error(transparent)]
161    Document(#[from] DocumentError),
162
163    /// Snapshot witness verification failed.
164    #[error("snapshot witness failed: {source}")]
165    Snapshot {
166        /// Underlying snapshot failure.
167        #[source]
168        source: Box<SnapshotError>,
169    },
170
171    /// Structured query reexecution failed.
172    #[error("query reexecution failed: {source}")]
173    Query {
174        /// Underlying deterministic query failure.
175        #[source]
176        source: Box<QueryError>,
177    },
178
179    /// Proof bytes violate the canonical format.
180    #[error("invalid result proof: {reason}")]
181    Invalid {
182        /// Stable diagnostic reason.
183        reason: &'static str,
184    },
185
186    /// Proof format is newer than this verifier.
187    #[error("unsupported result-proof format {found}; supported format is {supported}")]
188    UnsupportedVersion {
189        /// Version found in proof bytes.
190        found: u16,
191        /// Highest supported version.
192        supported: u16,
193    },
194
195    /// Proof file exceeds caller policy.
196    #[error("result proof is {actual} bytes; verification limit is {maximum}")]
197    ProofLimitExceeded {
198        /// Observed proof bytes.
199        actual: u64,
200        /// Configured maximum.
201        maximum: u64,
202    },
203
204    /// A canonical length or count cannot be represented safely.
205    #[error("result-proof length overflow")]
206    LengthOverflow,
207
208    /// Fast accidental-corruption check failed.
209    #[error("result-proof CRC32C mismatch")]
210    ChecksumMismatch,
211
212    /// Canonical proof content digest failed.
213    #[error("result-proof BLAKE3 mismatch")]
214    DigestMismatch,
215
216    /// Proof anchor did not match caller-pinned trust state.
217    #[error("result-proof anchor does not match the trusted anchor digest")]
218    AnchorMismatch,
219
220    /// Snapshot metadata does not match the proof anchor.
221    #[error("snapshot witness does not match the result-proof anchor")]
222    SnapshotAnchorMismatch,
223
224    /// Embedded operation and result variants disagree.
225    #[error("result-proof operation and result variants do not match")]
226    OperationResultMismatch,
227
228    /// Deterministic replay did not reproduce the embedded result.
229    #[error("offline reexecution does not match the result proof")]
230    ReexecutionMismatch,
231
232    /// End-to-end cooperative verification deadline expired.
233    #[error("result-proof verification timed out")]
234    TimedOut,
235}
236
237impl From<SnapshotError> for ProofError {
238    fn from(source: SnapshotError) -> Self {
239        Self::Snapshot {
240            source: Box::new(source),
241        }
242    }
243}
244
245impl From<QueryError> for ProofError {
246    fn from(source: QueryError) -> Self {
247        Self::Query {
248            source: Box::new(source),
249        }
250    }
251}