use vstd::prelude::*;
verus! {
pub struct AuditEntry {
pub operation: u64,
pub prev_hash: u64,
pub hash: u64,
}
pub struct AuditSink {
pub max_log_len: usize,
pub log: Vec<AuditEntry>,
pub last_hash: u64,
}
impl AuditSink {
pub open spec fn hash_spec(prev: u64, op: u64) -> int {
((prev as int) * 3 + ((op as int) % 100) + 1) % 100
}
pub fn hash_exec(prev: u64, op: u64) -> (h: u64)
ensures
h as int == Self::hash_spec(prev, op),
h < 100,
{
((prev % 100) * 3 + (op % 100) + 1) % 100
}
pub open spec fn type_invariant(&self) -> bool {
self.log.len() <= self.max_log_len
}
pub open spec fn chain_integrity(&self) -> bool {
forall|i: int|
#![trigger self.log@[i]]
1 <= i < self.log.len() ==> self.log@[i].prev_hash == self.log@[i - 1].hash
}
pub open spec fn hash_consistency(&self) -> bool {
if self.log.len() > 0 {
self.last_hash == self.log@[self.log.len() - 1].hash
} else {
self.last_hash == 0
}
}
pub open spec fn hash_binds_content(&self) -> bool {
forall|i: int|
#![trigger self.log@[i]]
0 <= i < self.log.len()
==> self.log@[i].hash as int
== Self::hash_spec(self.log@[i].prev_hash, self.log@[i].operation)
}
pub open spec fn last_hash_bounded(&self) -> bool {
self.last_hash < 100
}
pub open spec fn genesis_consistency(&self) -> bool {
self.log.len() > 0 ==> self.log@[0].prev_hash == 0
}
pub open spec fn inv(&self) -> bool {
&&& self.type_invariant()
&&& self.chain_integrity()
&&& self.hash_consistency()
&&& self.hash_binds_content()
&&& self.last_hash_bounded()
&&& self.genesis_consistency()
}
pub fn new(max_log_len: usize) -> (s: AuditSink)
ensures
s.max_log_len == max_log_len,
s.log@.len() == 0,
s.last_hash == 0,
s.inv(),
{
AuditSink { max_log_len, log: Vec::new(), last_hash: 0 }
}
pub fn record(&mut self, op: u64) -> (ok: bool)
requires old(self).inv(),
ensures
final(self).inv(),
final(self).max_log_len == old(self).max_log_len,
ok == (old(self).log.len() < old(self).max_log_len),
ok ==> {
&&& final(self).log@.len() == old(self).log@.len() + 1
&&& final(self).last_hash as int == Self::hash_spec(old(self).last_hash, op)
&&& final(self).log@[old(self).log@.len() as int].operation == op
&&& final(self).log@[old(self).log@.len() as int].prev_hash == old(self).last_hash
&&& forall|i: int|
#![trigger final(self).log@[i]]
0 <= i < old(self).log@.len() ==> final(self).log@[i] == old(self).log@[i]
},
!ok ==> final(self).log@ == old(self).log@ && final(self).last_hash == old(self).last_hash,
{
if self.log.len() < self.max_log_len {
let new_hash = Self::hash_exec(self.last_hash, op);
let entry = AuditEntry { operation: op, prev_hash: self.last_hash, hash: new_hash };
assert(self.log@.len() > 0 ==> self.last_hash == self.log@[self.log@.len() - 1].hash);
self.log.push(entry);
self.last_hash = new_hash;
assert(self.chain_integrity()) by {
assert forall|i: int| #![trigger self.log@[i]]
1 <= i < self.log.len() implies self.log@[i].prev_hash == self.log@[i - 1].hash by {
if i < self.log.len() - 1 {
}
}
}
assert(self.hash_binds_content()) by {
assert forall|i: int| #![trigger self.log@[i]]
0 <= i < self.log.len() implies self.log@[i].hash as int
== Self::hash_spec(self.log@[i].prev_hash, self.log@[i].operation) by {
if i < self.log.len() - 1 {
}
}
}
assert(self.genesis_consistency());
true
} else {
false
}
}
pub fn validate(&self) -> (ok: bool)
ensures ok == self.inv(),
{
if self.log.len() > self.max_log_len {
return false;
}
let len = self.log.len();
let mut i: usize = 0;
let mut expected_prev: u64 = 0;
while i < len
invariant
i <= len,
len == self.log.len(),
self.log.len() <= self.max_log_len,
expected_prev < 100,
i == 0 ==> expected_prev == 0,
i > 0 ==> expected_prev == self.log@[i as int - 1].hash,
forall|k: int| 0 <= k < i ==> #[trigger] self.log@[k].hash as int
== Self::hash_spec(self.log@[k].prev_hash, self.log@[k].operation),
forall|k: int| 1 <= k < i ==> #[trigger] self.log@[k].prev_hash
== self.log@[k - 1].hash,
i > 0 ==> self.log@[0].prev_hash == 0,
decreases len - i,
{
if self.log[i].prev_hash != expected_prev {
return false;
}
let expected_hash = Self::hash_exec(self.log[i].prev_hash, self.log[i].operation);
if self.log[i].hash != expected_hash {
return false;
}
expected_prev = self.log[i].hash;
i = i + 1;
}
if self.last_hash != expected_prev {
return false;
}
true
}
}
}