Skip to main content

agent_scroll/
verify.rs

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}