1mod wire;
8
9#[cfg(test)]
10mod tests;
11
12use std::collections::BTreeSet;
13use std::fmt;
14use std::sync::Arc;
15
16use crate::index::InterfaceNode;
17use crate::{Bar, CertificateLimits, ChangeColumn, InterfaceMode, PersistenceIndex};
18
19use wire::{encode_diagram, encode_header, encode_nodes, put_u64, put_usize};
20
21const SNAPSHOT_MAGIC: &[u8; 8] = b"HOLOSIP\0";
22const DELTA_MAGIC: &[u8; 8] = b"HOLOSDP\0";
23const VERSION: u16 = 4;
24const F64_BITS_CODEC: u8 = 1;
25
26#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct IndexProofError {
29 message: String,
30}
31
32impl IndexProofError {
33 fn new(message: impl Into<String>) -> Self {
34 Self {
35 message: message.into(),
36 }
37 }
38
39 pub fn message(&self) -> &str {
41 &self.message
42 }
43}
44
45impl fmt::Display for IndexProofError {
46 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
47 write!(formatter, "index proof: {}", self.message)
48 }
49}
50
51impl std::error::Error for IndexProofError {}
52
53#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
55pub struct IndexProofSummary {
56 pub nodes: usize,
58 pub composed_nodes: usize,
60 pub relative_nodes: usize,
62 pub relative_bytes: usize,
64 pub edge_changes: usize,
66 pub edge_columns: usize,
68 pub triangle_columns: usize,
70 pub higher_columns: usize,
72 pub terms: usize,
74}
75
76#[derive(Debug, Clone)]
77struct ProofNode {
78 digest: [u8; 32],
79 vertices: Vec<usize>,
80 edge_positions: Vec<usize>,
81 separator: Vec<usize>,
82 protected_vertices: Vec<usize>,
83 children: Vec<[u8; 32]>,
84 mode: InterfaceMode,
85 graded_columns: Vec<Vec<ChangeColumn>>,
86 relative_artifact: Vec<u8>,
87 relative_column_counts: Vec<usize>,
88 relative_terms: usize,
89 diagram: Vec<Bar>,
90}
91
92impl ProofNode {
93 fn from_interface(
94 node: &InterfaceNode,
95 max_dim: usize,
96 limits: CertificateLimits,
97 ) -> Result<Self, IndexProofError> {
98 let graded_columns = node
99 .reduction()
100 .map(|reduction| reduction.graded_columns().to_vec())
101 .unwrap_or_else(|| vec![Vec::new(); max_dim + 1]);
102 let relative_artifact = node
103 .relative()
104 .map(|relative| relative.encode(limits))
105 .transpose()
106 .map_err(|error| IndexProofError::new(error.to_string()))?
107 .unwrap_or_default();
108 let relative_column_counts = node
109 .relative()
110 .map(|relative| relative.graded_columns().iter().map(Vec::len).collect())
111 .unwrap_or_default();
112 let relative_terms = node
113 .relative()
114 .map(|relative| {
115 relative
116 .graded_columns()
117 .iter()
118 .flatten()
119 .map(|column| column.terms.len())
120 .sum()
121 })
122 .unwrap_or(0);
123 Ok(Self {
124 digest: node.digest,
125 vertices: node.vertices.clone(),
126 edge_positions: node.edge_positions.clone(),
127 separator: node.separator.clone(),
128 protected_vertices: node.protected_vertices.clone(),
129 children: node.children.iter().map(|child| child.digest).collect(),
130 mode: node.mode(),
131 graded_columns,
132 relative_artifact,
133 relative_column_counts,
134 relative_terms,
135 diagram: node.diagram().bars.clone(),
136 })
137 }
138
139 fn summary(&self, summary: &mut IndexProofSummary) {
140 summary.nodes += 1;
141 if self.mode != InterfaceMode::Materialized {
142 summary.composed_nodes += 1;
143 }
144 if self.mode == InterfaceMode::Relative {
145 summary.relative_nodes += 1;
146 summary.relative_bytes += self.relative_artifact.len();
147 }
148 summary.edge_columns += self.graded_columns.first().map_or(0, Vec::len);
149 summary.edge_columns += self.relative_column_counts.first().copied().unwrap_or(0);
150 summary.triangle_columns += self.graded_columns.get(1).map_or(0, Vec::len);
151 summary.triangle_columns += self.relative_column_counts.get(1).copied().unwrap_or(0);
152 summary.higher_columns += self
153 .graded_columns
154 .iter()
155 .skip(2)
156 .map(Vec::len)
157 .sum::<usize>();
158 summary.higher_columns += self.relative_column_counts.iter().skip(2).sum::<usize>();
159 summary.terms += self
160 .graded_columns
161 .iter()
162 .flatten()
163 .map(|column| column.terms.len())
164 .sum::<usize>();
165 summary.terms += self.relative_terms;
166 }
167}
168
169#[derive(Debug, Clone)]
171pub struct IndexSnapshotProof {
172 max_dim: usize,
173 modulus: u32,
174 threshold: Option<f64>,
175 vertex_count: usize,
176 edges: Vec<(usize, usize, f64)>,
177 root: [u8; 32],
178 nodes: Vec<ProofNode>,
179 diagram: Vec<Bar>,
180}
181
182impl IndexSnapshotProof {
183 pub fn from_index(index: &PersistenceIndex) -> Result<Self, IndexProofError> {
185 let mut nodes = Vec::new();
186 let mut seen = BTreeSet::new();
187 let excluded = BTreeSet::new();
188 collect_nodes(
189 index.root(),
190 index.params().max_dim,
191 index.certificate_limits(),
192 &excluded,
193 &mut seen,
194 &mut nodes,
195 )?;
196 Ok(Self {
197 max_dim: index.params().max_dim,
198 modulus: index.params().modulus,
199 threshold: index.params().threshold,
200 vertex_count: index.graph().len(),
201 edges: index.graph().edges().collect(),
202 root: index.version(),
203 nodes,
204 diagram: index.diagram().bars.clone(),
205 })
206 }
207
208 pub fn root(&self) -> &[u8; 32] {
210 &self.root
211 }
212
213 pub fn summary(&self) -> IndexProofSummary {
215 proof_summary(&self.nodes, 0)
216 }
217
218 pub fn encode(&self) -> Result<Vec<u8>, IndexProofError> {
220 let mut output = Vec::new();
221 output.extend_from_slice(SNAPSHOT_MAGIC);
222 encode_header(
223 &mut output,
224 self.max_dim,
225 self.modulus,
226 self.threshold,
227 self.vertex_count,
228 )?;
229 put_usize(&mut output, self.edges.len())?;
230 put_usize(&mut output, self.nodes.len())?;
231 put_usize(&mut output, self.diagram.len())?;
232 output.extend_from_slice(&self.root);
233 for &(u, v, value) in &self.edges {
234 put_usize(&mut output, u)?;
235 put_usize(&mut output, v)?;
236 put_u64(&mut output, value.to_bits());
237 }
238 encode_nodes(&mut output, &self.nodes)?;
239 encode_diagram(&mut output, &self.diagram)?;
240 Ok(output)
241 }
242}
243
244#[derive(Debug, Clone)]
246pub struct IndexDeltaProof {
247 max_dim: usize,
248 modulus: u32,
249 threshold: Option<f64>,
250 vertex_count: usize,
251 edge_count: usize,
252 old_root: [u8; 32],
253 new_root: [u8; 32],
254 edge_changes: Vec<(usize, f64)>,
255 nodes: Vec<ProofNode>,
256 diagram: Vec<Bar>,
257}
258
259impl IndexDeltaProof {
260 pub fn between(
262 old: &PersistenceIndex,
263 new: &PersistenceIndex,
264 ) -> Result<Self, IndexProofError> {
265 if old.params().max_dim != new.params().max_dim
266 || old.params().modulus != new.params().modulus
267 || old.params().threshold.map(f64::to_bits) != new.params().threshold.map(f64::to_bits)
268 || old.graph().len() != new.graph().len()
269 || old.topology() != new.topology()
270 {
271 return Err(IndexProofError::new(
272 "a proof delta requires one field, threshold, and listed-edge envelope",
273 ));
274 }
275 let edge_changes = old
276 .topology()
277 .iter()
278 .enumerate()
279 .filter_map(|(position, edge)| {
280 let old_value = old.graph().get(edge.u, edge.v);
281 let new_value = new.graph().get(edge.u, edge.v);
282 (old_value.to_bits() != new_value.to_bits()).then_some((position, new_value))
283 })
284 .collect();
285 let mut old_nodes = BTreeSet::new();
286 collect_digests(old.root(), &mut old_nodes);
287 let mut nodes = Vec::new();
288 let mut seen = BTreeSet::new();
289 collect_nodes(
290 new.root(),
291 new.params().max_dim,
292 new.certificate_limits(),
293 &old_nodes,
294 &mut seen,
295 &mut nodes,
296 )?;
297 Ok(Self {
298 max_dim: old.params().max_dim,
299 modulus: old.params().modulus,
300 threshold: old.params().threshold,
301 vertex_count: old.graph().len(),
302 edge_count: old.topology().len(),
303 old_root: old.version(),
304 new_root: new.version(),
305 edge_changes,
306 nodes,
307 diagram: new.diagram().bars.clone(),
308 })
309 }
310
311 pub fn old_root(&self) -> &[u8; 32] {
313 &self.old_root
314 }
315
316 pub fn new_root(&self) -> &[u8; 32] {
318 &self.new_root
319 }
320
321 pub fn summary(&self) -> IndexProofSummary {
323 proof_summary(&self.nodes, self.edge_changes.len())
324 }
325
326 pub fn encode(&self) -> Result<Vec<u8>, IndexProofError> {
328 let mut output = Vec::new();
329 output.extend_from_slice(DELTA_MAGIC);
330 encode_header(
331 &mut output,
332 self.max_dim,
333 self.modulus,
334 self.threshold,
335 self.vertex_count,
336 )?;
337 put_usize(&mut output, self.edge_count)?;
338 put_usize(&mut output, self.edge_changes.len())?;
339 put_usize(&mut output, self.nodes.len())?;
340 put_usize(&mut output, self.diagram.len())?;
341 output.extend_from_slice(&self.old_root);
342 output.extend_from_slice(&self.new_root);
343 for &(position, value) in &self.edge_changes {
344 put_usize(&mut output, position)?;
345 put_u64(&mut output, value.to_bits());
346 }
347 encode_nodes(&mut output, &self.nodes)?;
348 encode_diagram(&mut output, &self.diagram)?;
349 Ok(output)
350 }
351}
352
353fn collect_nodes(
354 node: &Arc<InterfaceNode>,
355 max_dim: usize,
356 limits: CertificateLimits,
357 excluded: &BTreeSet<[u8; 32]>,
358 seen: &mut BTreeSet<[u8; 32]>,
359 output: &mut Vec<ProofNode>,
360) -> Result<(), IndexProofError> {
361 if excluded.contains(&node.digest) || !seen.insert(node.digest) {
362 return Ok(());
363 }
364 output.push(ProofNode::from_interface(node, max_dim, limits)?);
365 for child in &node.children {
366 collect_nodes(child, max_dim, limits, excluded, seen, output)?;
367 }
368 Ok(())
369}
370
371fn collect_digests(node: &Arc<InterfaceNode>, output: &mut BTreeSet<[u8; 32]>) {
372 if !output.insert(node.digest) {
373 return;
374 }
375 for child in &node.children {
376 collect_digests(child, output);
377 }
378}
379
380fn proof_summary(nodes: &[ProofNode], edge_changes: usize) -> IndexProofSummary {
381 let mut summary = IndexProofSummary {
382 edge_changes,
383 ..IndexProofSummary::default()
384 };
385 for node in nodes {
386 node.summary(&mut summary);
387 }
388 summary
389}