use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ArtifactType {
CertificateIssuance,
CertificateRevocation,
ThresholdSignature,
ThresholdEncryption,
DirectorRotation,
QuorumPolicy,
DirectorIdentity,
ArchiveRenewal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MerkleEntry {
pub sequence: u64,
pub timestamp: DateTime<Utc>,
pub artifact_type: ArtifactType,
pub artifact_hash: [u8; 32],
#[serde(default)]
pub metadata: serde_json::Value,
}
impl MerkleEntry {
pub fn new(
sequence: u64,
artifact_type: ArtifactType,
artifact_hash: [u8; 32],
) -> Self {
Self {
sequence,
timestamp: Utc::now(),
artifact_type,
artifact_hash,
metadata: serde_json::Value::Null,
}
}
pub fn entry_hash(&self) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(self.sequence.to_le_bytes());
let ts_micros = self.timestamp.timestamp_micros();
hasher.update(ts_micros.to_le_bytes());
hasher.update(self.artifact_hash);
let result = hasher.finalize();
let mut out = [0u8; 32];
out.copy_from_slice(&result);
out
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn entry_hash_is_deterministic() {
let e1 = MerkleEntry::new(1, ArtifactType::CertificateIssuance, [0u8; 32]);
let e2 = MerkleEntry::new(1, ArtifactType::CertificateIssuance, [0u8; 32]);
let now = Utc::now();
let mut a = e1;
let mut b = e2;
a.timestamp = now;
b.timestamp = now;
assert_eq!(a.entry_hash(), b.entry_hash());
}
#[test]
fn different_entries_different_hashes() {
let now = Utc::now();
let mut e1 = MerkleEntry::new(1, ArtifactType::CertificateIssuance, [0u8; 32]);
let mut e2 = MerkleEntry::new(2, ArtifactType::CertificateIssuance, [0u8; 32]);
e1.timestamp = now;
e2.timestamp = now;
assert_ne!(e1.entry_hash(), e2.entry_hash());
}
}