use sha2::{Sha256, Digest};
use uuid::Uuid;
use crate::crypto::{aead, primitives::{EntryOp, EntryPayload}};
use crate::crypto::kdf::MasterKeySet;
use crate::client::{state::KeywordState, trapdoor::TrapdoorEngine};
use crate::error::VaultError;
use crate::server::edb::RawEdbEntry;
pub use crate::crypto::primitives::Tag;
pub struct UpdateEngine<'a> {
pub trapdoor: TrapdoorEngine<'a>,
}
impl<'a> UpdateEngine<'a> {
pub fn new(keys: &'a MasterKeySet) -> Self {
Self { trapdoor: TrapdoorEngine::new(keys) }
}
pub fn prepare_add(
&self,
keyword: &[u8],
doc_id: Uuid,
state: &mut KeywordState,
) -> Result<RawEdbEntry, VaultError> {
let idx = state.next_index();
let epoch = state.epoch;
let tag = self.trapdoor.derive_tag(keyword, idx, epoch);
let val_key = self.trapdoor.derive_val_key(keyword, idx, epoch);
let payload = EntryPayload {
doc_id,
op: EntryOp::Add,
timestamp: now_ms(),
};
let plaintext = bincode::serialize(&payload)?;
let enc_value = aead::encrypt(&val_key, &plaintext)?;
state.record_add(idx, doc_id);
Ok(RawEdbEntry { tag, value: enc_value })
}
pub fn prepare_delete(
&self,
keyword: &[u8],
doc_id: Uuid,
state: &mut KeywordState,
) -> Result<DeleteBatch, VaultError> {
let old_epoch = state.epoch;
let old_tags: Vec<Tag> = state
.live_indices
.iter()
.map(|&i| self.trapdoor.derive_tag(keyword, i, old_epoch))
.collect();
let was_indexed = state.evict_doc(doc_id);
if !was_indexed {
return Ok(DeleteBatch { removes: vec![], adds: vec![] });
}
let new_epoch = state.epoch;
let mut new_entries = Vec::with_capacity(state.live_indices.len());
for &idx in &state.live_indices {
let surviving_doc = Uuid::from_bytes(
*state.index_to_doc.get(&idx)
.ok_or(VaultError::DocNotFound(format!("index {idx}")))?
);
let tag = self.trapdoor.derive_tag(keyword, idx, new_epoch);
let val_key = self.trapdoor.derive_val_key(keyword, idx, new_epoch);
let payload = EntryPayload {
doc_id: surviving_doc,
op: EntryOp::Add,
timestamp: now_ms(),
};
let plaintext = bincode::serialize(&payload)?;
let enc_value = aead::encrypt(&val_key, &plaintext)?;
new_entries.push(RawEdbEntry { tag, value: enc_value });
}
Ok(DeleteBatch {
removes: old_tags,
adds: new_entries,
})
}
}
pub struct DeleteBatch {
pub removes: Vec<Tag>,
pub adds: Vec<RawEdbEntry>,
}
pub fn hash_keyword(keyword: &str) -> [u8; 32] {
let mut h = Sha256::new();
h.update(keyword.as_bytes());
h.finalize().into()
}
#[cfg(target_arch = "wasm32")]
pub fn now_ms() -> u64 {
(js_sys::Date::now()) as u64
}
#[cfg(not(target_arch = "wasm32"))]
pub fn now_ms() -> u64 {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_millis() as u64)
.unwrap_or(0)
}