warg_transparency/log/
vec_log.rs

1use core::fmt::Debug;
2use std::marker::PhantomData;
3
4use alloc::{vec, vec::Vec};
5use anyhow::Error;
6use prost::Message;
7
8use warg_crypto::hash::{Hash, SupportedDigest};
9use warg_crypto::VisitBytes;
10use warg_protobuf::internal as protobuf;
11
12use super::node::{Node, Side};
13use super::{hash_branch, hash_empty, hash_leaf, Checkpoint, LogBuilder, LogData};
14
15/// A verifiable log where the node hashes are stored
16/// contiguously in memory by index.
17#[derive(Debug, Clone)]
18pub struct VecLog<D, V>
19where
20    D: SupportedDigest,
21    V: VisitBytes,
22{
23    /// The number of entries
24    length: usize,
25    /// The tree data structure
26    tree: Vec<Hash<D>>,
27    /// Marker for value type
28    _value: PhantomData<V>,
29}
30
31/// Height is the number of child-edges between the node and leaves
32/// A leaf has height 0
33///
34/// Length is the number of total leaf nodes present
35impl<D, V> VecLog<D, V>
36where
37    D: SupportedDigest,
38    V: VisitBytes,
39{
40    /// Returns the number of entries in the log.
41    pub fn length(&self) -> usize {
42        self.length
43    }
44
45    fn get_digest(&self, node: Node) -> Hash<D> {
46        self.tree[node.index()].clone()
47    }
48
49    fn set_digest(&mut self, node: Node, digest: Hash<D>) {
50        self.tree[node.index()] = digest;
51    }
52
53    /// Get the root of the log when it was at some length
54    fn root_at(&self, length: usize) -> Option<Hash<D>> {
55        if length > self.length {
56            return None;
57        }
58
59        let roots = Node::broots_for_len(length);
60
61        let result = roots
62            .into_iter()
63            .rev()
64            .map(|node| self.hash_for(node).unwrap())
65            .reduce(|old, new| {
66                // Ordering due to reversal of iterator
67                hash_branch::<D>(new, old)
68            })
69            .unwrap_or_else(hash_empty::<D>);
70
71        Some(result)
72    }
73
74    /// Turn a VecLog into bytes using protobuf
75    pub fn to_protobuf(self) -> Vec<u8> {
76        let proto: protobuf::VecLog = self.into();
77        proto.encode_to_vec()
78    }
79
80    /// Parse a VecLog from bytes using protobuf
81    pub fn from_protobuf(bytes: &[u8]) -> Result<Self, Error> {
82        let proto = protobuf::VecLog::decode(bytes)?;
83        let value = proto.try_into()?;
84        Ok(value)
85    }
86}
87
88impl<D, V> Default for VecLog<D, V>
89where
90    D: SupportedDigest,
91    V: VisitBytes,
92{
93    fn default() -> Self {
94        VecLog {
95            length: 0,
96            tree: vec![],
97            _value: PhantomData,
98        }
99    }
100}
101
102impl<D, V> LogBuilder<D, V> for VecLog<D, V>
103where
104    D: SupportedDigest,
105    V: VisitBytes,
106{
107    fn checkpoint(&self) -> Checkpoint<D> {
108        Checkpoint {
109            root: self.root_at(self.length).unwrap(),
110            length: self.length,
111        }
112    }
113
114    fn push(&mut self, entry: &V) -> Node {
115        // Compute entry digest
116        let leaf_digest = hash_leaf::<D>(entry);
117
118        // Record entry
119        self.length += 1;
120
121        // Push spacer (if necessary) and entry digest
122        if self.length != 1 {
123            self.tree.push(hash_empty::<D>());
124        }
125        let leaf_node = Node(self.tree.len());
126        self.tree.push(leaf_digest.clone());
127
128        // Fill in newly known hashes
129        let mut current_digest = leaf_digest;
130        let mut current_node = leaf_node;
131        while current_node.side() == Side::Right {
132            let sibling = current_node.left_sibling();
133            let parent = current_node.parent();
134
135            let lhs = self.get_digest(sibling);
136            let rhs = current_digest;
137
138            current_digest = hash_branch::<D>(lhs, rhs);
139            current_node = parent;
140
141            self.set_digest(current_node, current_digest.clone());
142        }
143
144        leaf_node
145    }
146}
147
148impl<D, V> AsRef<[Hash<D>]> for VecLog<D, V>
149where
150    D: SupportedDigest,
151    V: VisitBytes,
152{
153    fn as_ref(&self) -> &[Hash<D>] {
154        &self.tree[..]
155    }
156}
157
158impl<D, V> LogData<D, V> for VecLog<D, V>
159where
160    D: SupportedDigest,
161    V: VisitBytes,
162{
163    fn hash_for(&self, node: Node) -> Option<Hash<D>> {
164        self.tree.get(node.index()).cloned()
165    }
166
167    fn has_hash(&self, node: Node) -> bool {
168        self.tree.len() > node.index()
169    }
170}
171
172impl<D, V> From<VecLog<D, V>> for protobuf::VecLog
173where
174    D: SupportedDigest,
175    V: VisitBytes,
176{
177    fn from(value: VecLog<D, V>) -> Self {
178        let tree = value
179            .tree
180            .into_iter()
181            .map(|hash| hash.bytes().to_vec())
182            .collect();
183        protobuf::VecLog {
184            length: value.length as u32,
185            tree,
186        }
187    }
188}
189
190impl<D, V> TryFrom<protobuf::VecLog> for VecLog<D, V>
191where
192    D: SupportedDigest,
193    V: VisitBytes,
194{
195    type Error = Error;
196
197    fn try_from(value: protobuf::VecLog) -> Result<Self, Self::Error> {
198        let length = value.length as usize;
199        let mut tree = Vec::with_capacity(length);
200        for entry in value.tree {
201            tree.push(entry.try_into()?)
202        }
203        let vec_log = VecLog {
204            length,
205            tree,
206            _value: PhantomData,
207        };
208        Ok(vec_log)
209    }
210}
211
212#[cfg(test)]
213mod tests {
214    use warg_crypto::hash::Sha256;
215
216    use crate::log::proof::InclusionProofError;
217
218    use super::*;
219
220    fn naive_merkle<D: SupportedDigest, E: VisitBytes>(elements: &[E]) -> Hash<D> {
221        match elements.len() {
222            0 => hash_empty::<D>(),
223            1 => hash_leaf::<D>(&elements[0]),
224            _ => {
225                let k = elements.len().next_power_of_two() / 2;
226                let left = naive_merkle::<D, E>(&elements[..k]);
227                let right = naive_merkle::<D, E>(&elements[k..]);
228                hash_branch::<D>(left, right)
229            }
230        }
231    }
232
233    #[test]
234    fn test_log_modifications() {
235        let data = [
236            "93", "67", "30", "37", "23", "75", "57", "89", "76", "42", "9", "14", "40", "59",
237            "26", "66", "77", "38", "47", "34", "8", "81", "101", "102", "103",
238        ];
239
240        let mut tree: VecLog<Sha256, &str> = VecLog::default();
241        let mut roots = Vec::new();
242
243        for i in 0..data.len() {
244            tree.push(&data[i]);
245
246            let naive_root = naive_merkle::<Sha256, _>(&data[..i + 1]);
247
248            let tree_root = tree.checkpoint().root();
249            assert_eq!(
250                tree_root, naive_root,
251                "at {}: (in-order) {:?} != (naive) {:?}",
252                i, tree_root, naive_root
253            );
254
255            roots.push(tree_root);
256        }
257
258        // Check inclusion proofs
259        for (i, _) in data.iter().enumerate() {
260            let leaf_node = Node(i * 2);
261
262            for (j, root) in roots.iter().enumerate() {
263                let log_length = j + 1;
264                let inc_proof = tree.prove_inclusion(leaf_node, log_length);
265                let result = inc_proof.evaluate_value(&tree, &data[i]);
266                if j >= i {
267                    assert!(result.is_ok());
268                    assert_eq!(root.clone(), result.unwrap());
269                } else {
270                    assert!(result.is_err());
271                    assert_eq!(result.unwrap_err(), InclusionProofError::LeafTooNew);
272                }
273            }
274        }
275
276        // Check consistency proofs
277        for (i, _) in data.iter().enumerate() {
278            let old_length = i + 1;
279            let old_root = tree.root_at(old_length).unwrap();
280
281            for (j, new_root) in roots.iter().enumerate().skip(i) {
282                let new_root = new_root.clone();
283                let new_length = j + 1;
284
285                let proof = tree.prove_consistency(old_length, new_length);
286                let results = proof.evaluate(&tree).unwrap();
287                let (found_old_root, found_new_root) = results;
288                assert_eq!(old_root, found_old_root);
289                assert_eq!(new_root, found_new_root);
290            }
291        }
292    }
293}