Skip to main content

automation_structures/primitives/
audit_sink.rs

1// Shared append-only chain owner for AuditSink and its named compositions.
2//
3// The chain operation is a verified parameter. BoundedHash is the public audit-hash instance;
4// AdditiveChain is the ReductionStream instance proved by ReductionStreamFromAuditSink.tla.
5
6use vstd::prelude::*;
7
8verus! {
9
10/// Operation used to extend an AuditSink chain.
11pub trait ChainOperation: Copy {
12    /// Whether the executable operation is defined for this input pair.
13    spec fn enabled(&self, previous: u64, operation: u64) -> bool;
14
15    /// Mathematical result of extending the chain.
16    spec fn combine_spec(&self, previous: u64, operation: u64) -> u64;
17
18    /// Executable form of `enabled`.
19    fn enabled_exec(&self, previous: u64, operation: u64) -> (enabled: bool)
20        ensures enabled == self.enabled(previous, operation);
21
22    /// Extend the chain.
23    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/// The bounded content-binding operation used by the public audit sink.
29#[derive(Clone, Copy)]
30pub struct BoundedHash;
31
32impl ChainOperation for BoundedHash {
33    open spec fn enabled(&self, _previous: u64, _operation: u64) -> bool {
34        true
35    }
36
37    open spec fn combine_spec(&self, previous: u64, operation: u64) -> u64 {
38        AuditSink::<BoundedHash>::hash_spec(previous, operation) as u64
39    }
40
41    fn enabled_exec(&self, _previous: u64, _operation: u64) -> (enabled: bool) {
42        true
43    }
44
45    fn combine(&self, previous: u64, operation: u64) -> (result: u64) {
46        AuditSink::<BoundedHash>::hash_exec(previous, operation)
47    }
48}
49
50/// Exact additive chain operation used by ReductionStream.
51#[derive(Clone, Copy)]
52pub struct AdditiveChain;
53
54impl ChainOperation for AdditiveChain {
55    open spec fn enabled(&self, previous: u64, operation: u64) -> bool {
56        previous as int + operation as int <= u64::MAX as int
57    }
58
59    open spec fn combine_spec(&self, previous: u64, operation: u64) -> u64 {
60        (previous + operation) as u64
61    }
62
63    fn enabled_exec(&self, previous: u64, operation: u64) -> (enabled: bool) {
64        operation <= u64::MAX - previous
65    }
66
67    fn combine(&self, previous: u64, operation: u64) -> (result: u64) {
68        previous + operation
69    }
70}
71
72/// One audit record: the operation, predecessor chain value, and resulting chain value.
73pub struct AuditEntry {
74    /// Operation committed by this entry.
75    pub operation: u64,
76    /// Chain value immediately before the operation.
77    pub prev_hash: u64,
78    /// Chain value produced by the operation.
79    pub hash: u64,
80}
81
82/// An append-only chained log. The operation parameter defaults to the public bounded hash.
83pub struct AuditSink<O: ChainOperation = BoundedHash> {
84    /// Chain operation instance.
85    pub operator: O,
86    /// Maximum retained entry count.
87    pub max_log_len: usize,
88    /// Append-only audit entries.
89    pub log: Vec<AuditEntry>,
90    /// Chain value after the latest entry, or zero for an empty log.
91    pub last_hash: u64,
92}
93
94impl AuditSink<BoundedHash> {
95    /// Chain hash in the original AuditSink model's integer form.
96    pub open spec fn hash_spec(previous: u64, operation: u64) -> int {
97        ((previous as int) * 3 + ((operation as int) % 100) + 1) % 100
98    }
99
100    /// Execute the public bounded hash instance.
101    pub fn hash_exec(previous: u64, operation: u64) -> (result: u64)
102        ensures
103            result as int == Self::hash_spec(previous, operation),
104            result < 100,
105    {
106        ((previous % 100) * 3 + (operation % 100) + 1) % 100
107    }
108
109    /// Construct the public bounded-hash AuditSink.
110    pub fn new(max_log_len: usize) -> (sink: AuditSink<BoundedHash>)
111        ensures
112            sink.max_log_len == max_log_len,
113            sink.log@.len() == 0,
114            sink.last_hash == 0,
115            sink.inv(),
116    {
117        AuditSink::with_operator(max_log_len, BoundedHash)
118    }
119}
120
121impl<O: ChainOperation> AuditSink<O> {
122    /// Construct an empty chain for a verified operation instance.
123    pub fn with_operator(max_log_len: usize, operator: O) -> (sink: AuditSink<O>)
124        ensures
125            sink.operator == operator,
126            sink.max_log_len == max_log_len,
127            sink.log@.len() == 0,
128            sink.last_hash == 0,
129            sink.inv(),
130    {
131        AuditSink { operator, max_log_len, log: Vec::new(), last_hash: 0 }
132    }
133
134    /// Whether the retained log fits within its configured capacity.
135    pub open spec fn type_invariant(&self) -> bool {
136        self.log.len() <= self.max_log_len
137    }
138
139    /// Whether every non-genesis record links to its immediate predecessor.
140    pub open spec fn chain_integrity(&self) -> bool {
141        forall|index: int|
142            #![trigger self.log@[index]]
143            1 <= index < self.log.len() ==>
144                self.log@[index].prev_hash == self.log@[index - 1].hash
145    }
146
147    /// Whether the retained head agrees with the last record or the empty-chain value.
148    pub open spec fn hash_consistency(&self) -> bool {
149        if self.log.len() > 0 {
150            self.last_hash == self.log@[self.log.len() - 1].hash
151        } else {
152            self.last_hash == 0
153        }
154    }
155
156    /// Every entry is the configured chain operation applied to its own content.
157    pub open spec fn hash_binds_content(&self) -> bool {
158        forall|index: int|
159            #![trigger self.log@[index]]
160            0 <= index < self.log.len() ==>
161                self.log@[index].hash
162                    == self.operator.combine_spec(
163                        self.log@[index].prev_hash,
164                        self.log@[index].operation,
165                    )
166    }
167
168    /// Every stored operation was in the configured operation's executable domain.
169    pub open spec fn operations_enabled(&self) -> bool {
170        forall|index: int|
171            #![trigger self.log@[index]]
172            0 <= index < self.log.len() ==>
173                self.operator.enabled(
174                    self.log@[index].prev_hash,
175                    self.log@[index].operation,
176                )
177    }
178
179    /// Whether the first retained record links to the genesis hash.
180    pub open spec fn genesis_consistency(&self) -> bool {
181        self.log.len() > 0 ==> self.log@[0].prev_hash == 0
182    }
183
184    /// Whether all append-only chain obligations hold.
185    pub open spec fn inv(&self) -> bool {
186        &&& self.type_invariant()
187        &&& self.chain_integrity()
188        &&& self.hash_consistency()
189        &&& self.hash_binds_content()
190        &&& self.operations_enabled()
191        &&& self.genesis_consistency()
192    }
193
194    /// Append one operation through the chain owner.
195    pub fn record(&mut self, operation: u64) -> (accepted: bool)
196        requires
197            old(self).inv(),
198            old(self).operator.enabled(old(self).last_hash, operation),
199        ensures
200            final(self).inv(),
201            final(self).operator == old(self).operator,
202            final(self).max_log_len == old(self).max_log_len,
203            accepted == (old(self).log.len() < old(self).max_log_len),
204            accepted ==> {
205                &&& final(self).log@.len() == old(self).log@.len() + 1
206                &&& final(self).last_hash
207                    == old(self).operator.combine_spec(old(self).last_hash, operation)
208                &&& final(self).log@[old(self).log@.len() as int].operation == operation
209                &&& final(self).log@[old(self).log@.len() as int].prev_hash
210                    == old(self).last_hash
211                &&& forall|index: int|
212                    #![trigger final(self).log@[index]]
213                    0 <= index < old(self).log@.len() ==>
214                        final(self).log@[index] == old(self).log@[index]
215            },
216            !accepted ==>
217                final(self).log@ == old(self).log@
218                    && final(self).last_hash == old(self).last_hash,
219    {
220        if self.log.len() < self.max_log_len {
221            let new_hash = self.operator.combine(self.last_hash, operation);
222            let entry = AuditEntry {
223                operation,
224                prev_hash: self.last_hash,
225                hash: new_hash,
226            };
227            assert(self.log@.len() > 0 ==>
228                self.last_hash == self.log@[self.log@.len() - 1].hash);
229            self.log.push(entry);
230            self.last_hash = new_hash;
231            assert(self.chain_integrity()) by {
232                assert forall|index: int| #![trigger self.log@[index]]
233                    1 <= index < self.log.len() implies
234                        self.log@[index].prev_hash == self.log@[index - 1].hash by {
235                    if index < self.log.len() - 1 {
236                    }
237                }
238            }
239            assert(self.hash_binds_content()) by {
240                assert forall|index: int| #![trigger self.log@[index]]
241                    0 <= index < self.log.len() implies
242                        self.log@[index].hash == self.operator.combine_spec(
243                            self.log@[index].prev_hash,
244                            self.log@[index].operation,
245                        ) by {
246                    if index < self.log.len() - 1 {
247                    }
248                }
249            }
250            assert(self.operations_enabled()) by {
251                assert forall|index: int| #![trigger self.log@[index]]
252                    0 <= index < self.log.len() implies
253                        self.operator.enabled(
254                            self.log@[index].prev_hash,
255                            self.log@[index].operation,
256                        ) by {
257                    if index < self.log.len() - 1 {
258                    }
259                }
260            }
261            assert(self.genesis_consistency());
262            true
263        } else {
264            false
265        }
266    }
267
268    /// Recompute the entire configured chain from the zero genesis.
269    pub fn validate(&self) -> (valid: bool)
270        ensures valid == self.inv(),
271    {
272        if self.log.len() > self.max_log_len {
273            return false;
274        }
275
276        let length = self.log.len();
277        let mut index: usize = 0;
278        let mut expected_previous: u64 = 0;
279        while index < length
280            invariant
281                index <= length,
282                length == self.log.len(),
283                self.log.len() <= self.max_log_len,
284                index == 0 ==> expected_previous == 0,
285                index > 0 ==> expected_previous == self.log@[index as int - 1].hash,
286                forall|entry: int| 0 <= entry < index ==>
287                    #[trigger] self.log@[entry].hash == self.operator.combine_spec(
288                        self.log@[entry].prev_hash,
289                        self.log@[entry].operation,
290                    ),
291                forall|entry: int| 0 <= entry < index ==>
292                    #[trigger] self.operator.enabled(
293                        self.log@[entry].prev_hash,
294                        self.log@[entry].operation,
295                    ),
296                forall|entry: int| 1 <= entry < index ==>
297                    #[trigger] self.log@[entry].prev_hash == self.log@[entry - 1].hash,
298                index > 0 ==> self.log@[0].prev_hash == 0,
299            decreases length - index,
300        {
301            if self.log[index].prev_hash != expected_previous {
302                return false;
303            }
304            if !self.operator.enabled_exec(self.log[index].prev_hash, self.log[index].operation) {
305                return false;
306            }
307            let expected_hash = self.operator.combine(
308                self.log[index].prev_hash,
309                self.log[index].operation,
310            );
311            if self.log[index].hash != expected_hash {
312                return false;
313            }
314            expected_previous = self.log[index].hash;
315            index = index + 1;
316        }
317
318        if self.last_hash != expected_previous {
319            return false;
320        }
321        true
322    }
323}
324
325}