mod wire;
#[cfg(test)]
mod tests;
use std::collections::BTreeSet;
use std::fmt;
use std::sync::Arc;
use crate::index::InterfaceNode;
use crate::{Bar, CertificateLimits, ChangeColumn, InterfaceMode, PersistenceIndex};
use wire::{encode_diagram, encode_header, encode_nodes, put_u64, put_usize};
const SNAPSHOT_MAGIC: &[u8; 8] = b"HOLOSIP\0";
const DELTA_MAGIC: &[u8; 8] = b"HOLOSDP\0";
const VERSION: u16 = 4;
const F64_BITS_CODEC: u8 = 1;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IndexProofError {
message: String,
}
impl IndexProofError {
fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
}
}
pub fn message(&self) -> &str {
&self.message
}
}
impl fmt::Display for IndexProofError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "index proof: {}", self.message)
}
}
impl std::error::Error for IndexProofError {}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct IndexProofSummary {
pub nodes: usize,
pub composed_nodes: usize,
pub relative_nodes: usize,
pub relative_bytes: usize,
pub edge_changes: usize,
pub edge_columns: usize,
pub triangle_columns: usize,
pub higher_columns: usize,
pub terms: usize,
}
#[derive(Debug, Clone)]
struct ProofNode {
digest: [u8; 32],
vertices: Vec<usize>,
edge_positions: Vec<usize>,
separator: Vec<usize>,
protected_vertices: Vec<usize>,
children: Vec<[u8; 32]>,
mode: InterfaceMode,
graded_columns: Vec<Vec<ChangeColumn>>,
relative_artifact: Vec<u8>,
relative_column_counts: Vec<usize>,
relative_terms: usize,
diagram: Vec<Bar>,
}
impl ProofNode {
fn from_interface(
node: &InterfaceNode,
max_dim: usize,
limits: CertificateLimits,
) -> Result<Self, IndexProofError> {
let graded_columns = node
.reduction()
.map(|reduction| reduction.graded_columns().to_vec())
.unwrap_or_else(|| vec![Vec::new(); max_dim + 1]);
let relative_artifact = node
.relative()
.map(|relative| relative.encode(limits))
.transpose()
.map_err(|error| IndexProofError::new(error.to_string()))?
.unwrap_or_default();
let relative_column_counts = node
.relative()
.map(|relative| relative.graded_columns().iter().map(Vec::len).collect())
.unwrap_or_default();
let relative_terms = node
.relative()
.map(|relative| {
relative
.graded_columns()
.iter()
.flatten()
.map(|column| column.terms.len())
.sum()
})
.unwrap_or(0);
Ok(Self {
digest: node.digest,
vertices: node.vertices.clone(),
edge_positions: node.edge_positions.clone(),
separator: node.separator.clone(),
protected_vertices: node.protected_vertices.clone(),
children: node.children.iter().map(|child| child.digest).collect(),
mode: node.mode(),
graded_columns,
relative_artifact,
relative_column_counts,
relative_terms,
diagram: node.diagram().bars.clone(),
})
}
fn summary(&self, summary: &mut IndexProofSummary) {
summary.nodes += 1;
if self.mode != InterfaceMode::Materialized {
summary.composed_nodes += 1;
}
if self.mode == InterfaceMode::Relative {
summary.relative_nodes += 1;
summary.relative_bytes += self.relative_artifact.len();
}
summary.edge_columns += self.graded_columns.first().map_or(0, Vec::len);
summary.edge_columns += self.relative_column_counts.first().copied().unwrap_or(0);
summary.triangle_columns += self.graded_columns.get(1).map_or(0, Vec::len);
summary.triangle_columns += self.relative_column_counts.get(1).copied().unwrap_or(0);
summary.higher_columns += self
.graded_columns
.iter()
.skip(2)
.map(Vec::len)
.sum::<usize>();
summary.higher_columns += self.relative_column_counts.iter().skip(2).sum::<usize>();
summary.terms += self
.graded_columns
.iter()
.flatten()
.map(|column| column.terms.len())
.sum::<usize>();
summary.terms += self.relative_terms;
}
}
#[derive(Debug, Clone)]
pub struct IndexSnapshotProof {
max_dim: usize,
modulus: u32,
threshold: Option<f64>,
vertex_count: usize,
edges: Vec<(usize, usize, f64)>,
root: [u8; 32],
nodes: Vec<ProofNode>,
diagram: Vec<Bar>,
}
impl IndexSnapshotProof {
pub fn from_index(index: &PersistenceIndex) -> Result<Self, IndexProofError> {
let mut nodes = Vec::new();
let mut seen = BTreeSet::new();
let excluded = BTreeSet::new();
collect_nodes(
index.root(),
index.params().max_dim,
index.certificate_limits(),
&excluded,
&mut seen,
&mut nodes,
)?;
Ok(Self {
max_dim: index.params().max_dim,
modulus: index.params().modulus,
threshold: index.params().threshold,
vertex_count: index.graph().len(),
edges: index.graph().edges().collect(),
root: index.version(),
nodes,
diagram: index.diagram().bars.clone(),
})
}
pub fn root(&self) -> &[u8; 32] {
&self.root
}
pub fn summary(&self) -> IndexProofSummary {
proof_summary(&self.nodes, 0)
}
pub fn encode(&self) -> Result<Vec<u8>, IndexProofError> {
let mut output = Vec::new();
output.extend_from_slice(SNAPSHOT_MAGIC);
encode_header(
&mut output,
self.max_dim,
self.modulus,
self.threshold,
self.vertex_count,
)?;
put_usize(&mut output, self.edges.len())?;
put_usize(&mut output, self.nodes.len())?;
put_usize(&mut output, self.diagram.len())?;
output.extend_from_slice(&self.root);
for &(u, v, value) in &self.edges {
put_usize(&mut output, u)?;
put_usize(&mut output, v)?;
put_u64(&mut output, value.to_bits());
}
encode_nodes(&mut output, &self.nodes)?;
encode_diagram(&mut output, &self.diagram)?;
Ok(output)
}
}
#[derive(Debug, Clone)]
pub struct IndexDeltaProof {
max_dim: usize,
modulus: u32,
threshold: Option<f64>,
vertex_count: usize,
edge_count: usize,
old_root: [u8; 32],
new_root: [u8; 32],
edge_changes: Vec<(usize, f64)>,
nodes: Vec<ProofNode>,
diagram: Vec<Bar>,
}
impl IndexDeltaProof {
pub fn between(
old: &PersistenceIndex,
new: &PersistenceIndex,
) -> Result<Self, IndexProofError> {
if old.params().max_dim != new.params().max_dim
|| old.params().modulus != new.params().modulus
|| old.params().threshold.map(f64::to_bits) != new.params().threshold.map(f64::to_bits)
|| old.graph().len() != new.graph().len()
|| old.topology() != new.topology()
{
return Err(IndexProofError::new(
"a proof delta requires one field, threshold, and listed-edge envelope",
));
}
let edge_changes = old
.topology()
.iter()
.enumerate()
.filter_map(|(position, edge)| {
let old_value = old.graph().get(edge.u, edge.v);
let new_value = new.graph().get(edge.u, edge.v);
(old_value.to_bits() != new_value.to_bits()).then_some((position, new_value))
})
.collect();
let mut old_nodes = BTreeSet::new();
collect_digests(old.root(), &mut old_nodes);
let mut nodes = Vec::new();
let mut seen = BTreeSet::new();
collect_nodes(
new.root(),
new.params().max_dim,
new.certificate_limits(),
&old_nodes,
&mut seen,
&mut nodes,
)?;
Ok(Self {
max_dim: old.params().max_dim,
modulus: old.params().modulus,
threshold: old.params().threshold,
vertex_count: old.graph().len(),
edge_count: old.topology().len(),
old_root: old.version(),
new_root: new.version(),
edge_changes,
nodes,
diagram: new.diagram().bars.clone(),
})
}
pub fn old_root(&self) -> &[u8; 32] {
&self.old_root
}
pub fn new_root(&self) -> &[u8; 32] {
&self.new_root
}
pub fn summary(&self) -> IndexProofSummary {
proof_summary(&self.nodes, self.edge_changes.len())
}
pub fn encode(&self) -> Result<Vec<u8>, IndexProofError> {
let mut output = Vec::new();
output.extend_from_slice(DELTA_MAGIC);
encode_header(
&mut output,
self.max_dim,
self.modulus,
self.threshold,
self.vertex_count,
)?;
put_usize(&mut output, self.edge_count)?;
put_usize(&mut output, self.edge_changes.len())?;
put_usize(&mut output, self.nodes.len())?;
put_usize(&mut output, self.diagram.len())?;
output.extend_from_slice(&self.old_root);
output.extend_from_slice(&self.new_root);
for &(position, value) in &self.edge_changes {
put_usize(&mut output, position)?;
put_u64(&mut output, value.to_bits());
}
encode_nodes(&mut output, &self.nodes)?;
encode_diagram(&mut output, &self.diagram)?;
Ok(output)
}
}
fn collect_nodes(
node: &Arc<InterfaceNode>,
max_dim: usize,
limits: CertificateLimits,
excluded: &BTreeSet<[u8; 32]>,
seen: &mut BTreeSet<[u8; 32]>,
output: &mut Vec<ProofNode>,
) -> Result<(), IndexProofError> {
if excluded.contains(&node.digest) || !seen.insert(node.digest) {
return Ok(());
}
output.push(ProofNode::from_interface(node, max_dim, limits)?);
for child in &node.children {
collect_nodes(child, max_dim, limits, excluded, seen, output)?;
}
Ok(())
}
fn collect_digests(node: &Arc<InterfaceNode>, output: &mut BTreeSet<[u8; 32]>) {
if !output.insert(node.digest) {
return;
}
for child in &node.children {
collect_digests(child, output);
}
}
fn proof_summary(nodes: &[ProofNode], edge_changes: usize) -> IndexProofSummary {
let mut summary = IndexProofSummary {
edge_changes,
..IndexProofSummary::default()
};
for node in nodes {
node.summary(&mut summary);
}
summary
}