use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::time::SystemTime;
pub use blake3;
pub use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
pub use rs_merkle::{Hasher, MerkleProof, MerkleTree};
#[derive(Clone)]
pub struct Blake3Hasher;
impl Hasher for Blake3Hasher {
type Hash = [u8; 32];
fn hash(data: &[u8]) -> Self::Hash {
*blake3::hash(data).as_bytes()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuditEntry {
pub id: String,
pub timestamp: u64,
pub action: String,
pub actor: String,
pub resource: String,
pub metadata: serde_json::Value,
pub content_hash: String,
}
impl AuditEntry {
pub fn new(
action: impl Into<String>,
actor: impl Into<String>,
resource: impl Into<String>,
metadata: serde_json::Value,
) -> Self {
let id = uuid::Uuid::new_v4().to_string();
let timestamp = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
let mut entry = Self {
id,
timestamp,
action: action.into(),
actor: actor.into(),
resource: resource.into(),
metadata,
content_hash: String::new(),
};
entry.content_hash = entry.compute_hash();
entry
}
pub fn compute_hash(&self) -> String {
let content = format!(
"{}:{}:{}:{}:{}",
self.id, self.timestamp, self.action, self.actor, self.resource
);
let hash = blake3::hash(content.as_bytes());
hex::encode(hash.as_bytes())
}
pub fn to_bytes(&self) -> Vec<u8> {
serde_json::to_vec(self).unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SignedAuditEntry {
pub entry: AuditEntry,
pub signature: String,
pub signer_pubkey: String,
}
impl SignedAuditEntry {
pub fn sign(entry: AuditEntry, signing_key: &SigningKey) -> Self {
let signature = signing_key.sign(&entry.to_bytes());
let verifying_key = signing_key.verifying_key();
Self {
entry,
signature: hex::encode(signature.to_bytes()),
signer_pubkey: hex::encode(verifying_key.to_bytes()),
}
}
pub fn verify(&self) -> Result<bool> {
let pubkey_bytes: [u8; 32] = hex::decode(&self.signer_pubkey)?
.try_into()
.map_err(|_| anyhow::anyhow!("Invalid pubkey length"))?;
let verifying_key = VerifyingKey::from_bytes(&pubkey_bytes)?;
let sig_bytes: [u8; 64] = hex::decode(&self.signature)?
.try_into()
.map_err(|_| anyhow::anyhow!("Invalid signature length"))?;
let signature = Signature::from_bytes(&sig_bytes);
Ok(verifying_key
.verify(&self.entry.to_bytes(), &signature)
.is_ok())
}
}
pub struct AuditLog {
entries: Vec<SignedAuditEntry>,
signing_key: SigningKey,
}
impl AuditLog {
pub fn new() -> Self {
let signing_key = SigningKey::generate(&mut rand::thread_rng());
Self {
entries: Vec::new(),
signing_key,
}
}
pub fn with_key(signing_key: SigningKey) -> Self {
Self {
entries: Vec::new(),
signing_key,
}
}
pub fn public_key(&self) -> String {
hex::encode(self.signing_key.verifying_key().to_bytes())
}
pub fn append(&mut self, entry: AuditEntry) -> &SignedAuditEntry {
let signed = SignedAuditEntry::sign(entry, &self.signing_key);
self.entries.push(signed);
self.entries.last().unwrap()
}
pub fn merkle_root(&self) -> Option<String> {
if self.entries.is_empty() {
return None;
}
let leaves: Vec<[u8; 32]> = self
.entries
.iter()
.map(|e| Blake3Hasher::hash(&e.entry.to_bytes()))
.collect();
let tree = MerkleTree::<Blake3Hasher>::from_leaves(&leaves);
tree.root().map(hex::encode)
}
pub fn proof_for(&self, index: usize) -> Option<Vec<String>> {
if index >= self.entries.len() {
return None;
}
let leaves: Vec<[u8; 32]> = self
.entries
.iter()
.map(|e| Blake3Hasher::hash(&e.entry.to_bytes()))
.collect();
let tree = MerkleTree::<Blake3Hasher>::from_leaves(&leaves);
let proof = tree.proof(&[index]);
let proof_hashes: Vec<String> = proof.proof_hashes().iter().map(hex::encode).collect();
Some(proof_hashes)
}
pub fn verify_all(&self) -> Result<bool> {
for entry in &self.entries {
if !entry.verify()? {
return Ok(false);
}
}
Ok(true)
}
pub fn entries(&self) -> &[SignedAuditEntry] {
&self.entries
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
impl Default for AuditLog {
fn default() -> Self {
Self::new()
}
}
pub fn quick_hash(data: &[u8]) -> String {
hex::encode(blake3::hash(data).as_bytes())
}
pub fn hash_string(s: &str) -> String {
quick_hash(s.as_bytes())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_audit_entry() {
let entry = AuditEntry::new(
"CREATE",
"user-123",
"document-456",
serde_json::json!({"details": "test"}),
);
assert!(!entry.id.is_empty());
assert!(!entry.content_hash.is_empty());
}
#[test]
fn test_signed_entry() {
let signing_key = SigningKey::generate(&mut rand::thread_rng());
let entry = AuditEntry::new("TEST", "actor", "resource", serde_json::json!({}));
let signed = SignedAuditEntry::sign(entry, &signing_key);
assert!(signed.verify().unwrap());
}
#[test]
fn test_audit_log() {
let mut log = AuditLog::new();
log.append(AuditEntry::new(
"CREATE",
"user1",
"doc1",
serde_json::json!({}),
));
log.append(AuditEntry::new(
"UPDATE",
"user1",
"doc1",
serde_json::json!({}),
));
log.append(AuditEntry::new(
"DELETE",
"user2",
"doc1",
serde_json::json!({}),
));
assert_eq!(log.len(), 3);
assert!(log.merkle_root().is_some());
assert!(log.verify_all().unwrap());
}
#[test]
fn test_merkle_proof() {
let mut log = AuditLog::new();
for i in 0..5 {
log.append(AuditEntry::new(
format!("ACTION_{}", i),
"actor",
"resource",
serde_json::json!({}),
));
}
let proof = log.proof_for(2);
assert!(proof.is_some());
assert!(!proof.unwrap().is_empty());
}
}