use crate::GraphError;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GraphReceipt {
pub version: u8,
pub timestamp: DateTime<Utc>,
pub pre_state_hash: [u8; 32],
pub post_state_hash: [u8; 32],
pub delta_hash: [u8; 32],
pub signature_or_hash: [u8; 32],
}
pub type TransitionReceipt = GraphReceipt;
impl GraphReceipt {
pub fn new(pre_state_hash: [u8; 32], post_state_hash: [u8; 32], delta_hash: [u8; 32]) -> Self {
let timestamp = Utc::now();
let version = 1;
let mut hasher = blake3::Hasher::new();
hasher.update(&[version]);
hasher.update(&pre_state_hash);
hasher.update(&post_state_hash);
hasher.update(&delta_hash);
hasher.update(timestamp.to_rfc3339().as_bytes());
let signature_or_hash = hasher.finalize().into();
Self {
version,
timestamp,
pre_state_hash,
post_state_hash,
delta_hash,
signature_or_hash,
}
}
pub fn verify(&self) -> Result<(), GraphError> {
let mut hasher = blake3::Hasher::new();
hasher.update(&[self.version]);
hasher.update(&self.pre_state_hash);
hasher.update(&self.post_state_hash);
hasher.update(&self.delta_hash);
hasher.update(self.timestamp.to_rfc3339().as_bytes());
let expected_hash: [u8; 32] = hasher.finalize().into();
if self.signature_or_hash != expected_hash {
return Err(GraphError::VerificationFailed(
"Cryptographic signature mismatch on GraphReceipt".to_string(),
));
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct HookReceipt {
pub version: u8,
pub hook_name: String,
pub sparql_query: String,
pub passed: bool,
pub timestamp: DateTime<Utc>,
pub graph_state_hash: [u8; 32],
pub signature_or_hash: [u8; 32],
}
impl HookReceipt {
pub fn new(
hook_name: String, sparql_query: String, passed: bool, graph_state_hash: [u8; 32],
) -> Self {
let timestamp = Utc::now();
let version = 1;
let mut hasher = blake3::Hasher::new();
hasher.update(&[version]);
hasher.update(hook_name.as_bytes());
hasher.update(sparql_query.as_bytes());
hasher.update(&[passed as u8]);
hasher.update(&graph_state_hash);
hasher.update(timestamp.to_rfc3339().as_bytes());
let signature_or_hash = hasher.finalize().into();
Self {
version,
hook_name,
sparql_query,
passed,
timestamp,
graph_state_hash,
signature_or_hash,
}
}
pub fn verify(&self) -> Result<(), GraphError> {
let mut hasher = blake3::Hasher::new();
hasher.update(&[self.version]);
hasher.update(self.hook_name.as_bytes());
hasher.update(self.sparql_query.as_bytes());
hasher.update(&[self.passed as u8]);
hasher.update(&self.graph_state_hash);
hasher.update(self.timestamp.to_rfc3339().as_bytes());
let expected_hash: [u8; 32] = hasher.finalize().into();
if self.signature_or_hash != expected_hash {
return Err(GraphError::VerificationFailed(
"Cryptographic signature mismatch on HookReceipt".to_string(),
));
}
Ok(())
}
}
#[derive(Debug, Clone, Default)]
pub struct ReplayVerifier {
seen_signatures: HashSet<[u8; 32]>,
last_state_hash: Option<[u8; 32]>,
}
impl ReplayVerifier {
pub fn new(initial_state_hash: Option<[u8; 32]>) -> Self {
Self {
seen_signatures: HashSet::new(),
last_state_hash: initial_state_hash,
}
}
pub fn verify_transition(&mut self, receipt: &GraphReceipt) -> Result<(), GraphError> {
receipt.verify()?;
if !self.seen_signatures.insert(receipt.signature_or_hash) {
return Err(GraphError::VerificationFailed(format!(
"Replay detected: Transition receipt with signature {:?} has already been applied",
receipt.signature_or_hash
)));
}
if let Some(expected_pre_hash) = self.last_state_hash {
if receipt.pre_state_hash != expected_pre_hash {
return Err(GraphError::VerificationFailed(
format!(
"Chain discontinuity: receipt expects pre-state hash {:?}, but current state hash is {:?}",
receipt.pre_state_hash, expected_pre_hash
)
));
}
}
self.last_state_hash = Some(receipt.post_state_hash);
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct TransactionBundle {
pub receipts: Vec<GraphReceipt>,
pub hook_receipts: Vec<HookReceipt>,
pub bundle_hash: [u8; 32],
pub timestamp: DateTime<Utc>,
}
impl TransactionBundle {
pub fn new(receipts: Vec<GraphReceipt>, hook_receipts: Vec<HookReceipt>) -> Self {
let timestamp = Utc::now();
let mut hasher = blake3::Hasher::new();
hasher.update(timestamp.to_rfc3339().as_bytes());
for r in &receipts {
hasher.update(&r.signature_or_hash);
}
for h in &hook_receipts {
hasher.update(&h.signature_or_hash);
}
let bundle_hash = hasher.finalize().into();
Self {
receipts,
hook_receipts,
bundle_hash,
timestamp,
}
}
pub fn verify(&self) -> Result<(), GraphError> {
let mut hasher = blake3::Hasher::new();
hasher.update(self.timestamp.to_rfc3339().as_bytes());
for r in &self.receipts {
hasher.update(&r.signature_or_hash);
r.verify()?;
}
for h in &self.hook_receipts {
hasher.update(&h.signature_or_hash);
h.verify()?;
}
let expected: [u8; 32] = hasher.finalize().into();
if self.bundle_hash != expected {
return Err(GraphError::VerificationFailed(
"Transaction bundle cryptographic hash mismatch".to_string(),
));
}
Ok(())
}
}