warg_transparency/log/
proof_bundle.rs

1use alloc::vec::Vec;
2use anyhow::Error;
3use indexmap::IndexSet;
4use prost::Message;
5use std::marker::PhantomData;
6use warg_crypto::{
7    hash::{Hash, SupportedDigest},
8    VisitBytes,
9};
10use warg_protobuf::transparency as protobuf;
11
12use crate::log::{
13    node::Node,
14    proof::{ConsistencyProof, InclusionProof},
15    sparse_data::SparseLogData,
16    LogData,
17};
18
19/// A collection of inclusion proof info
20pub struct ProofBundle<D, V>
21where
22    D: SupportedDigest,
23    V: VisitBytes,
24{
25    log_length: u32,
26    consistent_lengths: Vec<u32>,
27    included_indices: Vec<Node>,
28    hashes: Vec<(Node, Hash<D>)>,
29    /// Marker for value type
30    _digest: PhantomData<D>,
31    /// Marker for value type
32    _value: PhantomData<V>,
33}
34
35impl<D, V> ProofBundle<D, V>
36where
37    D: SupportedDigest,
38    V: VisitBytes,
39{
40    /// Bundles inclusion proofs together
41    pub fn bundle(
42        consistency_proofs: Vec<ConsistencyProof<D, V>>,
43        inclusion_proofs: Vec<InclusionProof<D, V>>,
44        data: &impl LogData<D, V>,
45    ) -> Result<Self, Error> {
46        let mut log_length = None;
47        let mut nodes_needed = IndexSet::new();
48
49        let mut consistent_lengths = Vec::new();
50        for proof in consistency_proofs.iter() {
51            consistent_lengths.push(proof.old_length as u32);
52            for proof in proof.inclusions()? {
53                if let Some(log_length) = log_length {
54                    if log_length != proof.log_length() {
55                        return Err(Error::msg("Bundle must contain proofs for the same root"));
56                    }
57                } else {
58                    log_length = Some(proof.log_length());
59                }
60                let walk = proof.walk()?;
61                for walk_index in walk.nodes {
62                    nodes_needed.insert(walk_index);
63                }
64                // Consistency proofs also need the leaf hash for each inclusion
65                // proof in order to construct the old root and perform evaluations
66                nodes_needed.insert(proof.leaf());
67            }
68        }
69
70        let mut included_indices = Vec::new();
71        for proof in inclusion_proofs.iter() {
72            included_indices.push(proof.leaf());
73            if let Some(log_length) = log_length {
74                if log_length != proof.log_length() {
75                    return Err(Error::msg("Bundle must contain proofs for the same root"));
76                }
77            } else {
78                log_length = Some(proof.log_length());
79            }
80            let walk = proof.walk()?;
81            for walk_index in walk.nodes {
82                nodes_needed.insert(walk_index);
83            }
84        }
85
86        let mut nodes_needed: Vec<Node> = nodes_needed.into_iter().collect();
87        nodes_needed.sort();
88        let mut hashes = Vec::new();
89        for node in nodes_needed {
90            let hash = data
91                .hash_for(node)
92                .ok_or_else(|| Error::msg("Necessary hash not found"))?;
93            hashes.push((node, hash));
94        }
95
96        if let Some(log_length) = log_length {
97            Ok(ProofBundle {
98                log_length: log_length as u32,
99                consistent_lengths,
100                included_indices,
101                hashes,
102                _digest: PhantomData,
103                _value: PhantomData,
104            })
105        } else {
106            Err(Error::msg("A bundle can not be made from no proofs"))
107        }
108    }
109
110    /// Splits a bundle into its constituent inclusion proofs
111    #[allow(clippy::type_complexity)]
112    pub fn unbundle(
113        self,
114    ) -> (
115        SparseLogData<D, V>,
116        Vec<ConsistencyProof<D, V>>,
117        Vec<InclusionProof<D, V>>,
118    ) {
119        let data = SparseLogData::from(self.hashes);
120
121        let c_proofs = self
122            .consistent_lengths
123            .into_iter()
124            .map(|len| ConsistencyProof::new(len as usize, self.log_length as usize))
125            .collect();
126
127        let i_proofs = self
128            .included_indices
129            .into_iter()
130            .map(|index| InclusionProof::new(index, self.log_length as usize))
131            .collect();
132
133        (data, c_proofs, i_proofs)
134    }
135
136    /// Turn a bundle into bytes using protobuf
137    pub fn encode(self) -> Vec<u8> {
138        let proto: protobuf::LogProofBundle = self.into();
139        proto.encode_to_vec()
140    }
141
142    /// Parse a bundle from bytes using protobuf
143    pub fn decode(bytes: &[u8]) -> Result<Self, Error> {
144        let proto = protobuf::LogProofBundle::decode(bytes)?;
145        let bundle = proto.try_into()?;
146        Ok(bundle)
147    }
148}
149
150impl<D, V> From<ProofBundle<D, V>> for protobuf::LogProofBundle
151where
152    D: SupportedDigest,
153    V: VisitBytes,
154{
155    fn from(value: ProofBundle<D, V>) -> Self {
156        let included_indices = value
157            .included_indices
158            .into_iter()
159            .map(|node| node.0 as u32)
160            .collect();
161        let hashes = value
162            .hashes
163            .into_iter()
164            .map(|(node, hash)| protobuf::HashEntry {
165                index: node.0 as u32,
166                hash: hash.bytes().to_vec(),
167            })
168            .collect();
169        protobuf::LogProofBundle {
170            log_length: value.log_length,
171            consistent_lengths: value.consistent_lengths,
172            included_indices,
173            hashes,
174        }
175    }
176}
177
178impl<D, V> TryFrom<protobuf::LogProofBundle> for ProofBundle<D, V>
179where
180    D: SupportedDigest,
181    V: VisitBytes,
182{
183    type Error = Error;
184
185    fn try_from(value: protobuf::LogProofBundle) -> Result<Self, Self::Error> {
186        let included_indices = value
187            .included_indices
188            .into_iter()
189            .map(|index| Node(index as usize))
190            .collect();
191        let mut hashes = Vec::new();
192        for entry in value.hashes {
193            hashes.push((Node(entry.index as usize), entry.hash.try_into()?))
194        }
195        let bundle = ProofBundle {
196            log_length: value.log_length,
197            consistent_lengths: value.consistent_lengths,
198            included_indices,
199            hashes,
200            _digest: PhantomData,
201            _value: PhantomData,
202        };
203        Ok(bundle)
204    }
205}