use std::cell::RefCell;
use crate::zisklib::KECCAKF_CACHE_INDEX_NOT_FOUND;
pub const KECCAKF_STATE_WORDS: usize = 25;
const INITIAL_SLOTS: usize = 1 << 10;
const MIX_KEYS: [u64; 5] = [
0xa076_1d64_78bd_642f,
0xe703_7ed1_a0b4_28db,
0x8ebc_6af0_9c88_c6e3,
0x5899_65cc_7537_4cc3,
0x1d8e_4e27_c47d_124f,
];
#[inline(always)]
fn mix(a: u64, b: u64) -> u64 {
let r = (a as u128).wrapping_mul(b as u128);
(r as u64) ^ ((r >> 64) as u64)
}
#[inline]
fn fingerprint(state: &[u64]) -> u64 {
debug_assert_eq!(state.len(), KECCAKF_STATE_WORDS);
let mut l0 = MIX_KEYS[0];
let mut l1 = MIX_KEYS[1];
let mut l2 = MIX_KEYS[2];
let mut l3 = MIX_KEYS[3];
let mut i = 0;
while i < KECCAKF_STATE_WORDS - 1 {
l0 = mix(l0 ^ state[i], MIX_KEYS[1]);
l1 = mix(l1 ^ state[i + 1], MIX_KEYS[2]);
l2 = mix(l2 ^ state[i + 2], MIX_KEYS[3]);
l3 = mix(l3 ^ state[i + 3], MIX_KEYS[4]);
i += 4;
}
let hash =
mix(l0 ^ l1, l2 ^ l3) ^ mix(state[KECCAKF_STATE_WORDS - 1] ^ MIX_KEYS[0], MIX_KEYS[4]);
if hash == 0 {
1
} else {
hash
}
}
#[derive(Clone, Copy, Debug, Default)]
struct Slot {
hash: u64,
index: u64,
offset: usize,
}
#[derive(Debug, Default)]
pub struct KeccakfCache {
slots: Vec<Slot>,
states: Vec<u64>,
len: usize,
pending_index: Option<u64>,
}
impl KeccakfCache {
pub fn set_pending_index(&mut self, index: u64) {
if index == KECCAKF_CACHE_INDEX_NOT_FOUND {
panic!(
"KeccakfCache::set_pending_index() called with the reserved not-found index {index:#x}"
);
}
self.pending_index = Some(index);
}
pub fn take_pending_index(&mut self) -> Option<u64> {
self.pending_index.take()
}
pub fn store(&mut self, state: &[u64], index: u64) {
assert_eq!(
state.len(),
KECCAKF_STATE_WORDS,
"KeccakfCache::store() called with {} words",
state.len()
);
if (self.len + 1) * 2 > self.slots.len() {
self.grow();
}
let hash = fingerprint(state);
let mask = self.slots.len() - 1;
let mut slot = hash as usize & mask;
while self.slots[slot].hash != 0 {
if self.slots[slot].hash == hash && self.state_at(self.slots[slot].offset) == state {
self.slots[slot].index = index;
return;
}
slot = (slot + 1) & mask;
}
let offset = self.states.len();
self.states.extend_from_slice(state);
self.slots[slot] = Slot { hash, index, offset };
self.len += 1;
}
pub fn get(&self, state: &[u64]) -> u64 {
assert_eq!(
state.len(),
KECCAKF_STATE_WORDS,
"KeccakfCache::get() called with {} words",
state.len()
);
if self.slots.is_empty() {
return KECCAKF_CACHE_INDEX_NOT_FOUND;
}
let hash = fingerprint(state);
let mask = self.slots.len() - 1;
let mut slot = hash as usize & mask;
while self.slots[slot].hash != 0 {
if self.slots[slot].hash == hash && self.state_at(self.slots[slot].offset) == state {
return self.slots[slot].index;
}
slot = (slot + 1) & mask;
}
KECCAKF_CACHE_INDEX_NOT_FOUND
}
pub fn clear(&mut self) {
self.slots = Vec::new();
self.states = Vec::new();
self.len = 0;
self.pending_index = None;
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
fn state_at(&self, offset: usize) -> &[u64] {
&self.states[offset..offset + KECCAKF_STATE_WORDS]
}
fn grow(&mut self) {
let new_len = if self.slots.is_empty() { INITIAL_SLOTS } else { self.slots.len() * 2 };
let old_slots = std::mem::replace(&mut self.slots, vec![Slot::default(); new_len]);
let mask = new_len - 1;
for entry in old_slots.iter().filter(|slot| slot.hash != 0) {
let mut slot = entry.hash as usize & mask;
while self.slots[slot].hash != 0 {
slot = (slot + 1) & mask;
}
self.slots[slot] = *entry;
}
}
}
thread_local! {
static NATIVE_CACHE: RefCell<KeccakfCache> = RefCell::new(KeccakfCache::default());
}
pub fn keccakf_cache_set_index(index: u64) {
NATIVE_CACHE.with(|cache| cache.borrow_mut().set_pending_index(index));
}
pub fn keccakf_cache_get_index(state: &[u64; KECCAKF_STATE_WORDS]) -> u64 {
NATIVE_CACHE.with(|cache| cache.borrow().get(state))
}
pub fn keccakf_cache_on_keccakf(state: &[u64; KECCAKF_STATE_WORDS]) {
NATIVE_CACHE.with(|cache| {
let mut cache = cache.borrow_mut();
if let Some(index) = cache.take_pending_index() {
cache.store(state, index);
}
});
}
pub fn keccakf_cache_clear() {
NATIVE_CACHE.with(|cache| cache.borrow_mut().clear());
}
#[cfg(test)]
mod tests {
use super::*;
fn state(seed: u64) -> [u64; KECCAKF_STATE_WORDS] {
let mut state = [0u64; KECCAKF_STATE_WORDS];
for (i, word) in state.iter_mut().enumerate() {
*word = seed.wrapping_mul(0x9e37_79b9_7f4a_7c15).wrapping_add(i as u64);
}
state
}
#[test]
fn miss_on_empty_cache() {
let cache = KeccakfCache::default();
assert_eq!(cache.get(&state(1)), KECCAKF_CACHE_INDEX_NOT_FOUND);
assert!(cache.is_empty());
}
#[test]
fn stores_and_finds_states() {
let mut cache = KeccakfCache::default();
for i in 0..1000u64 {
cache.store(&state(i), i * 7);
}
assert_eq!(cache.len(), 1000);
for i in 0..1000u64 {
assert_eq!(cache.get(&state(i)), i * 7);
}
assert_eq!(cache.get(&state(1000)), KECCAKF_CACHE_INDEX_NOT_FOUND);
}
#[test]
fn distinguishes_states_differing_in_one_word() {
let mut cache = KeccakfCache::default();
let base = state(42);
cache.store(&base, 5);
for i in 0..KECCAKF_STATE_WORDS {
let mut other = base;
other[i] ^= 1;
assert_eq!(cache.get(&other), KECCAKF_CACHE_INDEX_NOT_FOUND, "word {i}");
}
assert_eq!(cache.get(&base), 5);
}
#[test]
fn restoring_a_state_updates_its_index() {
let mut cache = KeccakfCache::default();
cache.store(&state(3), 1);
cache.store(&state(3), 2);
assert_eq!(cache.len(), 1);
assert_eq!(cache.get(&state(3)), 2);
}
#[test]
fn pending_index_is_consumed_once() {
let mut cache = KeccakfCache::default();
assert_eq!(cache.take_pending_index(), None);
cache.set_pending_index(9);
assert_eq!(cache.take_pending_index(), Some(9));
assert_eq!(cache.take_pending_index(), None);
}
#[test]
#[should_panic(expected = "reserved not-found index")]
fn rejects_the_reserved_index() {
KeccakfCache::default().set_pending_index(KECCAKF_CACHE_INDEX_NOT_FOUND);
}
#[test]
fn native_fcalls_round_trip() {
use crate::{
syscalls::syscall_keccak_f,
zisklib::{fcall_get_keccakf_cache_index, fcall_set_keccakf_cache_index},
};
#[cfg(feature = "hints")]
let mut hints = Vec::new();
keccakf_cache_clear();
let mut permuted = state(7);
let input = permuted;
assert_eq!(fcall_get_keccakf_cache_index(&input), KECCAKF_CACHE_INDEX_NOT_FOUND);
fcall_set_keccakf_cache_index(11);
unsafe {
syscall_keccak_f(
&mut permuted,
#[cfg(feature = "hints")]
&mut hints,
)
};
assert_eq!(fcall_get_keccakf_cache_index(&input), 11);
assert_eq!(fcall_get_keccakf_cache_index(&permuted), KECCAKF_CACHE_INDEX_NOT_FOUND);
let mut other = state(8);
let other_input = other;
unsafe {
syscall_keccak_f(
&mut other,
#[cfg(feature = "hints")]
&mut hints,
)
};
assert_eq!(fcall_get_keccakf_cache_index(&other_input), KECCAKF_CACHE_INDEX_NOT_FOUND);
keccakf_cache_clear();
}
#[test]
fn clear_empties_the_cache() {
let mut cache = KeccakfCache::default();
cache.store(&state(1), 1);
cache.set_pending_index(2);
cache.clear();
assert!(cache.is_empty());
assert_eq!(cache.take_pending_index(), None);
assert_eq!(cache.get(&state(1)), KECCAKF_CACHE_INDEX_NOT_FOUND);
}
}