1use base64::{engine::general_purpose::STANDARD, Engine};
2use ed25519_dalek::{Signature, Verifier, VerifyingKey};
3use serde_json::{Map, Value};
4
5use crate::canonical::{canonical, hash_canonical};
6use crate::schema::{validate_sealed_turn, VerifyFailure, VerifyResult};
7
8pub fn verify(chain: &[Value], pubkey: Option<&[u8; 32]>) -> VerifyResult {
9 let mut failures = Vec::new();
10 let mut prev_hash: Option<String> = None;
11
12 for (i, item) in chain.iter().enumerate() {
13 if let Err(err) = validate_sealed_turn(item) {
14 failures.push(VerifyFailure {
15 turn: i,
16 reason: "SchemaViolation",
17 detail: Some(err),
18 });
19 prev_hash = None;
20 continue;
21 }
22 let obj = item.as_object().unwrap();
23 let h = obj
24 .get("hash")
25 .and_then(|v| v.as_str())
26 .unwrap_or("")
27 .to_string();
28 let sig = obj.get("sig").cloned();
29 let mut turn_only: Map<String, Value> = obj.clone();
30 turn_only.remove("hash");
31 turn_only.remove("sig");
32 let turn_only_v = Value::Object(turn_only.clone());
33
34 match hash_canonical(&turn_only_v) {
35 Ok(computed) if computed == h => {}
36 _ => {
37 failures.push(VerifyFailure {
38 turn: i,
39 reason: "BadHash",
40 detail: None,
41 });
42 prev_hash = Some(h);
43 continue;
44 }
45 }
46
47 if i > 0 {
48 let got = turn_only
49 .get("prev_hash")
50 .and_then(|v| v.as_str())
51 .map(String::from);
52 if got != prev_hash {
53 failures.push(VerifyFailure {
54 turn: i,
55 reason: "BrokenChain",
56 detail: None,
57 });
58 }
59 }
60
61 if let (Some(sig), Some(pk_bytes)) = (sig, pubkey) {
62 let sig_b64 = sig.get("sig").and_then(|v| v.as_str()).unwrap_or("");
63 let canonical_bytes = match canonical(&turn_only_v) {
64 Ok(b) => b,
65 Err(_) => {
66 failures.push(VerifyFailure {
67 turn: i,
68 reason: "BadSignature",
69 detail: None,
70 });
71 prev_hash = Some(h);
72 continue;
73 }
74 };
75 let ok = (|| -> Option<()> {
76 let bytes = STANDARD.decode(sig_b64.as_bytes()).ok()?;
77 let arr: [u8; 64] = bytes.try_into().ok()?;
78 let s = Signature::from_bytes(&arr);
79 let vk = VerifyingKey::from_bytes(pk_bytes).ok()?;
80 vk.verify(&canonical_bytes, &s).ok()?;
81 Some(())
82 })()
83 .is_some();
84 if !ok {
85 failures.push(VerifyFailure {
86 turn: i,
87 reason: "BadSignature",
88 detail: None,
89 });
90 }
91 }
92
93 prev_hash = Some(h);
94 }
95
96 VerifyResult {
97 ok: failures.is_empty(),
98 failures,
99 }
100}