use vstd::prelude::*;
#[allow(unused_imports)]
use crate::primitives::audit_sink::{AdditiveChain, AuditSink, ChainOperation};
verus! {
pub open spec fn sum_to(s: Seq<u64>, n: int) -> int
decreases n,
{
if n <= 0 {
0
} else if n > s.len() as int {
0
} else {
s[n - 1] as int + sum_to(s, n - 1)
}
}
pub open spec fn sum_spec(s: Seq<u64>) -> int {
sum_to(s, s.len() as int)
}
pub proof fn lemma_sum_to_bounded(s: Seq<u64>, n: int)
requires
forall|k: int| 0 <= k < s.len() ==> s[k] <= 1_000_000_000u64,
0 <= n <= s.len() as int,
ensures
sum_to(s, n) <= 1_000_000_000 * n,
decreases n,
{
if n > 0 {
lemma_sum_to_bounded(s, n - 1);
}
}
proof fn lemma_sum_to_push_prefix(s: Seq<u64>, x: u64, n: int)
requires
0 <= n <= s.len() as int,
ensures
sum_to(s.push(x), n) == sum_to(s, n),
decreases n,
{
if n > 0 {
lemma_sum_to_push_prefix(s, x, n - 1);
assert(s.push(x)[n - 1] == s[n - 1]);
}
}
pub proof fn lemma_sum_push(s: Seq<u64>, x: u64)
ensures
sum_spec(s.push(x)) == sum_spec(s) + x as int,
{
let t = s.push(x);
let n = s.len() as int;
assert(t.len() == n + 1);
lemma_sum_to_push_prefix(s, x, n);
assert(t[n] == x);
assert(sum_to(t, n + 1) == t[n] as int + sum_to(t, n));
}
pub struct Reducer {
pub source: Vec<u64>,
pub audit: AuditSink<AdditiveChain>,
}
impl Reducer {
pub open spec fn prefix_binding(&self) -> bool {
&&& self.audit.log.len() <= self.source.len()
&&& forall|index: int|
#![trigger self.audit.log@[index]]
0 <= index < self.audit.log.len() ==>
self.audit.log@[index].operation == self.source@[index]
}
pub open spec fn aggregate(&self) -> bool {
self.audit.last_hash as int == sum_to(self.source@, self.audit.log.len() as int)
}
pub open spec fn bounded(&self) -> bool {
&&& forall|k: int| 0 <= k < self.source@.len() ==> self.source@[k] <= 1_000_000_000u64
&&& self.source@.len() <= 1_000_000_000
}
pub open spec fn inv(&self) -> bool {
&&& self.audit.inv()
&&& self.audit.max_log_len == self.source.len()
&&& self.prefix_binding()
&&& self.aggregate()
&&& self.bounded()
}
pub fn new(items: Vec<u64>) -> (r: Reducer)
requires
forall|k: int| 0 <= k < items@.len() ==> items@[k] <= 1_000_000_000u64,
items@.len() <= 1_000_000_000,
ensures
r.inv(),
r.source@ == items@,
r.audit.log@.len() == 0,
r.audit.last_hash == 0,
{
let audit = AuditSink::with_operator(items.len(), AdditiveChain);
let r = Reducer { source: items, audit };
proof {
assert(r.audit.log@.len() == 0);
assert(sum_to(r.source@, 0) == 0);
}
r
}
pub fn position(&self) -> (position: usize)
ensures position == self.audit.log@.len(),
{
self.audit.log.len()
}
pub fn result(&self) -> (result: u64)
ensures result == self.audit.last_hash,
{
self.audit.last_hash
}
pub fn remaining_len(&self) -> (remaining: usize)
requires self.prefix_binding(),
ensures remaining == self.source@.len() - self.audit.log@.len(),
{
self.source.len() - self.audit.log.len()
}
pub fn done(&self) -> (d: bool)
requires self.prefix_binding(),
ensures
d == (self.audit.log@.len() == self.source@.len()),
{
self.audit.log.len() == self.source.len()
}
pub fn process(&mut self)
requires
old(self).inv(),
old(self).audit.log@.len() < old(self).source@.len(),
ensures
final(self).inv(),
final(self).source@ == old(self).source@,
final(self).audit.log@.len() == old(self).audit.log@.len() + 1,
final(self).audit.last_hash
== old(self).audit.last_hash
+ old(self).source@[old(self).audit.log@.len() as int],
{
let old_position = self.audit.log.len();
let x = self.source[old_position];
let ghost old_log = self.audit.log@;
let ghost source = self.source@;
proof {
lemma_sum_to_bounded(self.source@, old_position as int);
assert(self.audit.last_hash as int
== sum_to(self.source@, old_position as int));
assert(self.audit.last_hash as int <= 1_000_000_000 * old_position as int);
assert(x <= 1_000_000_000u64);
assert(old_position < 1_000_000_000usize);
assert(self.audit.last_hash as int + x as int <= 1_000_000_000_000_000_000int);
assert(1_000_000_000_000_000_000int < u64::MAX as int);
assert(self.audit.operator.enabled(self.audit.last_hash, x));
}
let accepted = self.audit.record(x);
assert(accepted);
let _ = accepted;
proof {
assert(self.audit.log@[old_position as int].operation == x);
assert(self.prefix_binding()) by {
assert forall|index: int|
#![trigger self.audit.log@[index]]
0 <= index < self.audit.log.len() implies
self.audit.log@[index].operation == self.source@[index] by {
if index < old_position {
assert(self.audit.log@[index] == old_log[index]);
} else {
assert(index == old_position);
}
}
}
assert(sum_to(source, old_position as int + 1)
== source[old_position as int] as int
+ sum_to(source, old_position as int));
assert(self.aggregate());
}
}
}
pub open spec fn fold_to(s: Seq<u64>, n: int, identity: u64, op: spec_fn(u64, u64) -> u64) -> u64
decreases n,
{
if n <= 0 {
identity
} else if n > s.len() as int {
identity
} else {
op(fold_to(s, n - 1, identity, op), s[n - 1])
}
}
proof fn lemma_fold_to_bounded_sum(s: Seq<u64>, n: int)
requires
forall|k: int| 0 <= k < s.len() ==> s[k] <= 1_000_000_000u64,
0 <= n <= s.len() as int,
ensures
fold_to(s, n, 0, |a: u64, b: u64| (a + b) as u64) <= 1_000_000_000 * n,
decreases n,
{
if n > 0 {
lemma_fold_to_bounded_sum(s, n - 1);
}
}
pub fn reduce_sum(items: &[u64]) -> (result: u64)
requires
forall|k: int| 0 <= k < items@.len() ==> items@[k] <= 1_000_000_000u64,
items@.len() <= 1_000_000_000,
ensures
result as int == fold_to(items@, items@.len() as int, 0, |a: u64, b: u64| (a + b) as u64) as int,
{
let n: usize = items.len();
let mut result: u64 = 0;
let mut i: usize = 0;
while i < n
invariant
i <= n,
n == items@.len(),
n <= 1_000_000_000,
forall|k: int| 0 <= k < items@.len() ==> items@[k] <= 1_000_000_000u64,
result as int == fold_to(items@, i as int, 0, |a: u64, b: u64| (a + b) as u64) as int,
decreases n - i,
{
proof {
lemma_fold_to_bounded_sum(items@, i as int);
}
result = result + items[i];
i = i + 1;
}
result
}
pub fn reduce_max(items: &[u64]) -> (result: u64)
requires
forall|k: int| 0 <= k < items@.len() ==> items@[k] <= 1_000_000_000u64,
items@.len() <= 1_000_000_000,
ensures
result as int == fold_to(items@, items@.len() as int, 0, |a: u64, b: u64| if a > b { a } else { b }) as int,
{
let n: usize = items.len();
let mut result: u64 = 0;
let mut i: usize = 0;
while i < n
invariant
i <= n,
n == items@.len(),
forall|k: int| 0 <= k < items@.len() ==> items@[k] <= 1_000_000_000u64,
result <= 1_000_000_000u64,
result as int == fold_to(items@, i as int, 0, |a: u64, b: u64| if a > b { a } else { b }) as int,
decreases n - i,
{
if items[i] > result {
result = items[i];
}
i = i + 1;
}
result
}
}