warg_transparency/log/
proof_bundle.rs1use 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
19pub 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 _digest: PhantomData<D>,
31 _value: PhantomData<V>,
33}
34
35impl<D, V> ProofBundle<D, V>
36where
37 D: SupportedDigest,
38 V: VisitBytes,
39{
40 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 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 #[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 pub fn encode(self) -> Vec<u8> {
138 let proto: protobuf::LogProofBundle = self.into();
139 proto.encode_to_vec()
140 }
141
142 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}