use base64::{engine::general_purpose::STANDARD, Engine};
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use serde_json::{Map, Value};
use crate::canonical::{canonical, hash_canonical};
use crate::schema::{validate_sealed_turn, VerifyFailure, VerifyResult};
pub fn verify(chain: &[Value], pubkey: Option<&[u8; 32]>) -> VerifyResult {
let mut failures = Vec::new();
let mut prev_hash: Option<String> = None;
for (i, item) in chain.iter().enumerate() {
if let Err(err) = validate_sealed_turn(item) {
failures.push(VerifyFailure {
turn: i,
reason: "SchemaViolation",
detail: Some(err),
});
prev_hash = None;
continue;
}
let obj = item.as_object().unwrap();
let h = obj
.get("hash")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let sig = obj.get("sig").cloned();
let mut turn_only: Map<String, Value> = obj.clone();
turn_only.remove("hash");
turn_only.remove("sig");
let turn_only_v = Value::Object(turn_only.clone());
match hash_canonical(&turn_only_v) {
Ok(computed) if computed == h => {}
_ => {
failures.push(VerifyFailure {
turn: i,
reason: "BadHash",
detail: None,
});
prev_hash = Some(h);
continue;
}
}
if i > 0 {
let got = turn_only
.get("prev_hash")
.and_then(|v| v.as_str())
.map(String::from);
if got != prev_hash {
failures.push(VerifyFailure {
turn: i,
reason: "BrokenChain",
detail: None,
});
}
}
if let (Some(sig), Some(pk_bytes)) = (sig, pubkey) {
let sig_b64 = sig.get("sig").and_then(|v| v.as_str()).unwrap_or("");
let canonical_bytes = match canonical(&turn_only_v) {
Ok(b) => b,
Err(_) => {
failures.push(VerifyFailure {
turn: i,
reason: "BadSignature",
detail: None,
});
prev_hash = Some(h);
continue;
}
};
let ok = (|| -> Option<()> {
let bytes = STANDARD.decode(sig_b64.as_bytes()).ok()?;
let arr: [u8; 64] = bytes.try_into().ok()?;
let s = Signature::from_bytes(&arr);
let vk = VerifyingKey::from_bytes(pk_bytes).ok()?;
vk.verify(&canonical_bytes, &s).ok()?;
Some(())
})()
.is_some();
if !ok {
failures.push(VerifyFailure {
turn: i,
reason: "BadSignature",
detail: None,
});
}
}
prev_hash = Some(h);
}
VerifyResult {
ok: failures.is_empty(),
failures,
}
}