use vstd::prelude::*;
verus! {
pub trait ChainOperation: Copy {
spec fn enabled(&self, previous: u64, operation: u64) -> bool;
spec fn combine_spec(&self, previous: u64, operation: u64) -> u64;
fn enabled_exec(&self, previous: u64, operation: u64) -> (enabled: bool)
ensures enabled == self.enabled(previous, operation);
fn combine(&self, previous: u64, operation: u64) -> (result: u64)
requires self.enabled(previous, operation),
ensures result == self.combine_spec(previous, operation);
}
#[derive(Clone, Copy)]
pub struct BoundedHash;
impl ChainOperation for BoundedHash {
open spec fn enabled(&self, _previous: u64, _operation: u64) -> bool {
true
}
open spec fn combine_spec(&self, previous: u64, operation: u64) -> u64 {
AuditSink::<BoundedHash>::hash_spec(previous, operation) as u64
}
fn enabled_exec(&self, _previous: u64, _operation: u64) -> (enabled: bool) {
true
}
fn combine(&self, previous: u64, operation: u64) -> (result: u64) {
AuditSink::<BoundedHash>::hash_exec(previous, operation)
}
}
#[derive(Clone, Copy)]
pub struct AdditiveChain;
impl ChainOperation for AdditiveChain {
open spec fn enabled(&self, previous: u64, operation: u64) -> bool {
previous as int + operation as int <= u64::MAX as int
}
open spec fn combine_spec(&self, previous: u64, operation: u64) -> u64 {
(previous + operation) as u64
}
fn enabled_exec(&self, previous: u64, operation: u64) -> (enabled: bool) {
operation <= u64::MAX - previous
}
fn combine(&self, previous: u64, operation: u64) -> (result: u64) {
previous + operation
}
}
pub struct AuditEntry {
pub operation: u64,
pub prev_hash: u64,
pub hash: u64,
}
pub struct AuditSink<O: ChainOperation = BoundedHash> {
pub operator: O,
pub max_log_len: usize,
pub log: Vec<AuditEntry>,
pub last_hash: u64,
}
impl AuditSink<BoundedHash> {
pub open spec fn hash_spec(previous: u64, operation: u64) -> int {
((previous as int) * 3 + ((operation as int) % 100) + 1) % 100
}
pub fn hash_exec(previous: u64, operation: u64) -> (result: u64)
ensures
result as int == Self::hash_spec(previous, operation),
result < 100,
{
((previous % 100) * 3 + (operation % 100) + 1) % 100
}
pub fn new(max_log_len: usize) -> (sink: AuditSink<BoundedHash>)
ensures
sink.max_log_len == max_log_len,
sink.log@.len() == 0,
sink.last_hash == 0,
sink.inv(),
{
AuditSink::with_operator(max_log_len, BoundedHash)
}
}
impl<O: ChainOperation> AuditSink<O> {
pub fn with_operator(max_log_len: usize, operator: O) -> (sink: AuditSink<O>)
ensures
sink.operator == operator,
sink.max_log_len == max_log_len,
sink.log@.len() == 0,
sink.last_hash == 0,
sink.inv(),
{
AuditSink { operator, max_log_len, log: Vec::new(), last_hash: 0 }
}
pub open spec fn type_invariant(&self) -> bool {
self.log.len() <= self.max_log_len
}
pub open spec fn chain_integrity(&self) -> bool {
forall|index: int|
#![trigger self.log@[index]]
1 <= index < self.log.len() ==>
self.log@[index].prev_hash == self.log@[index - 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|index: int|
#![trigger self.log@[index]]
0 <= index < self.log.len() ==>
self.log@[index].hash
== self.operator.combine_spec(
self.log@[index].prev_hash,
self.log@[index].operation,
)
}
pub open spec fn operations_enabled(&self) -> bool {
forall|index: int|
#![trigger self.log@[index]]
0 <= index < self.log.len() ==>
self.operator.enabled(
self.log@[index].prev_hash,
self.log@[index].operation,
)
}
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.operations_enabled()
&&& self.genesis_consistency()
}
pub fn record(&mut self, operation: u64) -> (accepted: bool)
requires
old(self).inv(),
old(self).operator.enabled(old(self).last_hash, operation),
ensures
final(self).inv(),
final(self).operator == old(self).operator,
final(self).max_log_len == old(self).max_log_len,
accepted == (old(self).log.len() < old(self).max_log_len),
accepted ==> {
&&& final(self).log@.len() == old(self).log@.len() + 1
&&& final(self).last_hash
== old(self).operator.combine_spec(old(self).last_hash, operation)
&&& final(self).log@[old(self).log@.len() as int].operation == operation
&&& final(self).log@[old(self).log@.len() as int].prev_hash
== old(self).last_hash
&&& forall|index: int|
#![trigger final(self).log@[index]]
0 <= index < old(self).log@.len() ==>
final(self).log@[index] == old(self).log@[index]
},
!accepted ==>
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.operator.combine(self.last_hash, operation);
let entry = AuditEntry {
operation,
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|index: int| #![trigger self.log@[index]]
1 <= index < self.log.len() implies
self.log@[index].prev_hash == self.log@[index - 1].hash by {
if index < self.log.len() - 1 {
}
}
}
assert(self.hash_binds_content()) by {
assert forall|index: int| #![trigger self.log@[index]]
0 <= index < self.log.len() implies
self.log@[index].hash == self.operator.combine_spec(
self.log@[index].prev_hash,
self.log@[index].operation,
) by {
if index < self.log.len() - 1 {
}
}
}
assert(self.operations_enabled()) by {
assert forall|index: int| #![trigger self.log@[index]]
0 <= index < self.log.len() implies
self.operator.enabled(
self.log@[index].prev_hash,
self.log@[index].operation,
) by {
if index < self.log.len() - 1 {
}
}
}
assert(self.genesis_consistency());
true
} else {
false
}
}
pub fn validate(&self) -> (valid: bool)
ensures valid == self.inv(),
{
if self.log.len() > self.max_log_len {
return false;
}
let length = self.log.len();
let mut index: usize = 0;
let mut expected_previous: u64 = 0;
while index < length
invariant
index <= length,
length == self.log.len(),
self.log.len() <= self.max_log_len,
index == 0 ==> expected_previous == 0,
index > 0 ==> expected_previous == self.log@[index as int - 1].hash,
forall|entry: int| 0 <= entry < index ==>
#[trigger] self.log@[entry].hash == self.operator.combine_spec(
self.log@[entry].prev_hash,
self.log@[entry].operation,
),
forall|entry: int| 0 <= entry < index ==>
#[trigger] self.operator.enabled(
self.log@[entry].prev_hash,
self.log@[entry].operation,
),
forall|entry: int| 1 <= entry < index ==>
#[trigger] self.log@[entry].prev_hash == self.log@[entry - 1].hash,
index > 0 ==> self.log@[0].prev_hash == 0,
decreases length - index,
{
if self.log[index].prev_hash != expected_previous {
return false;
}
if !self.operator.enabled_exec(self.log[index].prev_hash, self.log[index].operation) {
return false;
}
let expected_hash = self.operator.combine(
self.log[index].prev_hash,
self.log[index].operation,
);
if self.log[index].hash != expected_hash {
return false;
}
expected_previous = self.log[index].hash;
index = index + 1;
}
if self.last_hash != expected_previous {
return false;
}
true
}
}
}