use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use serde::{Deserialize, Serialize};
use super::{
Attestation, AttestationError, compute_entry_hash, from_hex, genesis_hash, leaf_hash,
merkle_root, params_digest, seal_bytes,
};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct VerificationReport {
pub chain_ok: bool,
pub merkle_ok: bool,
pub signature_ok: bool,
pub entry_count: u64,
pub session_id: String,
pub bytes_egressed: u64,
pub public_key: String,
}
pub fn verify_attestation(json: &str) -> Result<VerificationReport, AttestationError> {
let attestation: Attestation =
serde_json::from_str(json).map_err(|err| AttestationError::Malformed(err.to_string()))?;
let session_id = from_hex::<16>(&attestation.session_id)?;
let mut chain_ok = true;
let mut prev = genesis_hash(&session_id);
let mut leaves: Vec<[u8; 32]> = Vec::with_capacity(attestation.operations.len());
for (index, op) in attestation.operations.iter().enumerate() {
let stored_prev = from_hex::<32>(&op.prev_hash)?;
let stored_entry = from_hex::<32>(&op.entry_hash)?;
if op.seq != index as u64 {
chain_ok = false;
}
if stored_prev != prev {
chain_ok = false;
}
let digest = params_digest(&op.params);
let recomputed = compute_entry_hash(op.seq, op.ts_ms, &op.op, &digest, &stored_prev);
if recomputed != stored_entry {
chain_ok = false;
}
leaves.push(leaf_hash(&stored_entry));
prev = stored_entry;
}
let claimed_head = from_hex::<32>(&attestation.head_hash)?;
let computed_head = if attestation.operations.is_empty() {
genesis_hash(&session_id)
} else {
prev
};
if computed_head != claimed_head {
chain_ok = false;
}
let claimed_root = from_hex::<32>(&attestation.merkle_root)?;
let computed_root = merkle_root(&leaves, &session_id);
let merkle_ok = computed_root == claimed_root;
let public_key = from_hex::<32>(&attestation.public_key)?;
let signature_bytes = from_hex::<64>(&attestation.signature)?;
let policy_digest = params_digest(&attestation.policy);
let entry_count = attestation.operations.len() as u64;
let message = seal_bytes(
attestation.version,
&session_id,
attestation.started_at_ms,
attestation.ended_at_ms,
entry_count,
&claimed_root,
&claimed_head,
attestation.bytes_egressed,
attestation.bytes_ingressed,
&policy_digest,
);
let signature_ok = match VerifyingKey::from_bytes(&public_key) {
Ok(verifying_key) => {
let signature = Signature::from_bytes(&signature_bytes);
verifying_key.verify(&message, &signature).is_ok()
}
Err(_) => false,
};
Ok(VerificationReport {
chain_ok,
merkle_ok,
signature_ok,
entry_count,
session_id: attestation.session_id,
bytes_egressed: attestation.bytes_egressed,
public_key: attestation.public_key,
})
}