automation_structures/primitives/
audit_sink.rs1use vstd::prelude::*;
7
8verus! {
9
10pub trait ChainOperation: Copy {
12 spec fn enabled(&self, previous: u64, operation: u64) -> bool;
14
15 spec fn combine_spec(&self, previous: u64, operation: u64) -> u64;
17
18 fn enabled_exec(&self, previous: u64, operation: u64) -> (enabled: bool)
20 ensures enabled == self.enabled(previous, operation);
21
22 fn combine(&self, previous: u64, operation: u64) -> (result: u64)
24 requires self.enabled(previous, operation),
25 ensures result == self.combine_spec(previous, operation);
26}
27
28#[derive(Clone, Copy)]
33pub struct BoundedHash;
34
35impl ChainOperation for BoundedHash {
36 open spec fn enabled(&self, _previous: u64, _operation: u64) -> bool {
37 true
38 }
39
40 open spec fn combine_spec(&self, previous: u64, operation: u64) -> u64 {
41 AuditSink::<BoundedHash>::hash_spec(previous, operation) as u64
42 }
43
44 fn enabled_exec(&self, _previous: u64, _operation: u64) -> (enabled: bool) {
45 true
46 }
47
48 fn combine(&self, previous: u64, operation: u64) -> (result: u64) {
49 AuditSink::<BoundedHash>::hash_exec(previous, operation)
50 }
51}
52
53#[derive(Clone, Copy)]
55pub struct AdditiveChain;
56
57impl ChainOperation for AdditiveChain {
58 open spec fn enabled(&self, previous: u64, operation: u64) -> bool {
59 previous as int + operation as int <= u64::MAX as int
60 }
61
62 open spec fn combine_spec(&self, previous: u64, operation: u64) -> u64 {
63 (previous + operation) as u64
64 }
65
66 fn enabled_exec(&self, previous: u64, operation: u64) -> (enabled: bool) {
67 operation <= u64::MAX - previous
68 }
69
70 fn combine(&self, previous: u64, operation: u64) -> (result: u64) {
71 previous + operation
72 }
73}
74
75pub struct AuditEntry {
77 pub operation: u64,
79 pub prev_hash: u64,
81 pub hash: u64,
83}
84
85pub struct AuditSink<O: ChainOperation = BoundedHash> {
87 pub operator: O,
89 pub max_log_len: usize,
91 pub log: Vec<AuditEntry>,
93 pub last_hash: u64,
95}
96
97impl AuditSink<BoundedHash> {
98 pub open spec fn hash_spec(previous: u64, operation: u64) -> int {
100 ((previous as int) * 3 + ((operation as int) % 100) + 1) % 100
101 }
102
103 pub fn hash_exec(previous: u64, operation: u64) -> (result: u64)
105 ensures
106 result as int == Self::hash_spec(previous, operation),
107 result < 100,
108 {
109 ((previous % 100) * 3 + (operation % 100) + 1) % 100
110 }
111
112 pub fn new(max_log_len: usize) -> (sink: AuditSink<BoundedHash>)
114 ensures
115 sink.max_log_len == max_log_len,
116 sink.log@.len() == 0,
117 sink.last_hash == 0,
118 sink.inv(),
119 {
120 AuditSink::with_operator(max_log_len, BoundedHash)
121 }
122}
123
124impl<O: ChainOperation> AuditSink<O> {
125 pub fn with_operator(max_log_len: usize, operator: O) -> (sink: AuditSink<O>)
127 ensures
128 sink.operator == operator,
129 sink.max_log_len == max_log_len,
130 sink.log@.len() == 0,
131 sink.last_hash == 0,
132 sink.inv(),
133 {
134 AuditSink { operator, max_log_len, log: Vec::new(), last_hash: 0 }
135 }
136
137 pub open spec fn type_invariant(&self) -> bool {
139 self.log.len() <= self.max_log_len
140 }
141
142 pub open spec fn chain_integrity(&self) -> bool {
144 forall|index: int|
145 #![trigger self.log@[index]]
146 1 <= index < self.log.len() ==>
147 self.log@[index].prev_hash == self.log@[index - 1].hash
148 }
149
150 pub open spec fn hash_consistency(&self) -> bool {
152 if self.log.len() > 0 {
153 self.last_hash == self.log@[self.log.len() - 1].hash
154 } else {
155 self.last_hash == 0
156 }
157 }
158
159 pub open spec fn hash_binds_content(&self) -> bool {
163 forall|index: int|
164 #![trigger self.log@[index]]
165 0 <= index < self.log.len() ==>
166 self.log@[index].hash
167 == self.operator.combine_spec(
168 self.log@[index].prev_hash,
169 self.log@[index].operation,
170 )
171 }
172
173 pub open spec fn operations_enabled(&self) -> bool {
175 forall|index: int|
176 #![trigger self.log@[index]]
177 0 <= index < self.log.len() ==>
178 self.operator.enabled(
179 self.log@[index].prev_hash,
180 self.log@[index].operation,
181 )
182 }
183
184 pub open spec fn genesis_consistency(&self) -> bool {
186 self.log.len() > 0 ==> self.log@[0].prev_hash == 0
187 }
188
189 pub open spec fn inv(&self) -> bool {
191 &&& self.type_invariant()
192 &&& self.chain_integrity()
193 &&& self.hash_consistency()
194 &&& self.hash_binds_content()
195 &&& self.operations_enabled()
196 &&& self.genesis_consistency()
197 }
198
199 pub fn record(&mut self, operation: u64) -> (accepted: bool)
201 requires
202 old(self).inv(),
203 old(self).operator.enabled(old(self).last_hash, operation),
204 ensures
205 final(self).inv(),
206 final(self).operator == old(self).operator,
207 final(self).max_log_len == old(self).max_log_len,
208 accepted == (old(self).log.len() < old(self).max_log_len),
209 accepted ==> {
210 &&& final(self).log@.len() == old(self).log@.len() + 1
211 &&& final(self).last_hash
212 == old(self).operator.combine_spec(old(self).last_hash, operation)
213 &&& final(self).log@[old(self).log@.len() as int].operation == operation
214 &&& final(self).log@[old(self).log@.len() as int].prev_hash
215 == old(self).last_hash
216 &&& forall|index: int|
217 #![trigger final(self).log@[index]]
218 0 <= index < old(self).log@.len() ==>
219 final(self).log@[index] == old(self).log@[index]
220 },
221 !accepted ==>
222 final(self).log@ == old(self).log@
223 && final(self).last_hash == old(self).last_hash,
224 {
225 if self.log.len() < self.max_log_len {
226 let new_hash = self.operator.combine(self.last_hash, operation);
227 let entry = AuditEntry {
228 operation,
229 prev_hash: self.last_hash,
230 hash: new_hash,
231 };
232 assert(self.log@.len() > 0 ==>
233 self.last_hash == self.log@[self.log@.len() - 1].hash);
234 self.log.push(entry);
235 self.last_hash = new_hash;
236 assert(self.chain_integrity()) by {
237 assert forall|index: int| #![trigger self.log@[index]]
238 1 <= index < self.log.len() implies
239 self.log@[index].prev_hash == self.log@[index - 1].hash by {
240 if index < self.log.len() - 1 {
241 }
242 }
243 }
244 assert(self.hash_binds_content()) by {
245 assert forall|index: int| #![trigger self.log@[index]]
246 0 <= index < self.log.len() implies
247 self.log@[index].hash == self.operator.combine_spec(
248 self.log@[index].prev_hash,
249 self.log@[index].operation,
250 ) by {
251 if index < self.log.len() - 1 {
252 }
253 }
254 }
255 assert(self.operations_enabled()) by {
256 assert forall|index: int| #![trigger self.log@[index]]
257 0 <= index < self.log.len() implies
258 self.operator.enabled(
259 self.log@[index].prev_hash,
260 self.log@[index].operation,
261 ) by {
262 if index < self.log.len() - 1 {
263 }
264 }
265 }
266 assert(self.genesis_consistency());
267 true
268 } else {
269 false
270 }
271 }
272
273 pub fn validate(&self) -> (valid: bool)
275 ensures valid == self.inv(),
276 {
277 if self.log.len() > self.max_log_len {
278 return false;
279 }
280
281 let length = self.log.len();
282 let mut index: usize = 0;
283 let mut expected_previous: u64 = 0;
284 while index < length
285 invariant
286 index <= length,
287 length == self.log.len(),
288 self.log.len() <= self.max_log_len,
289 index == 0 ==> expected_previous == 0,
290 index > 0 ==> expected_previous == self.log@[index as int - 1].hash,
291 forall|entry: int| 0 <= entry < index ==>
292 #[trigger] self.log@[entry].hash == self.operator.combine_spec(
293 self.log@[entry].prev_hash,
294 self.log@[entry].operation,
295 ),
296 forall|entry: int| 0 <= entry < index ==>
297 #[trigger] self.operator.enabled(
298 self.log@[entry].prev_hash,
299 self.log@[entry].operation,
300 ),
301 forall|entry: int| 1 <= entry < index ==>
302 #[trigger] self.log@[entry].prev_hash == self.log@[entry - 1].hash,
303 index > 0 ==> self.log@[0].prev_hash == 0,
304 decreases length - index,
305 {
306 if self.log[index].prev_hash != expected_previous {
307 return false;
308 }
309 if !self.operator.enabled_exec(self.log[index].prev_hash, self.log[index].operation) {
310 return false;
311 }
312 let expected_hash = self.operator.combine(
313 self.log[index].prev_hash,
314 self.log[index].operation,
315 );
316 if self.log[index].hash != expected_hash {
317 return false;
318 }
319 expected_previous = self.log[index].hash;
320 index = index + 1;
321 }
322
323 if self.last_hash != expected_previous {
324 return false;
325 }
326 true
327 }
328}
329
330}