Skip to main content

agent_scroll/
seal.rs

1use base64::{engine::general_purpose::STANDARD, Engine};
2use ed25519_dalek::{Signer, SigningKey};
3use serde_json::{json, Map, Value};
4
5use crate::canonical::{canonical, hash_canonical};
6use crate::error::Error;
7
8#[derive(Clone)]
9pub struct SignOpts {
10    pub privkey: [u8; 32],
11    pub pubkey: [u8; 32],
12}
13
14pub fn seal(turn: &Value, sign: Option<&SignOpts>) -> Result<Value, Error> {
15    let h = hash_canonical(turn)?;
16    let mut out = turn
17        .as_object()
18        .cloned()
19        .ok_or_else(|| Error::Invalid("turn not object".into()))?;
20    out.insert("hash".into(), json!(h));
21    if let Some(s) = sign {
22        let sk = SigningKey::from_bytes(&s.privkey);
23        let canonical_bytes = canonical(turn)?;
24        let sig = sk.sign(&canonical_bytes);
25        out.insert(
26            "sig".into(),
27            json!({
28                "alg": "ed25519",
29                "pubkey": STANDARD.encode(s.pubkey),
30                "sig": STANDARD.encode(sig.to_bytes()),
31            }),
32        );
33    }
34    Ok(Value::Object(out))
35}
36
37pub fn seal_chain(turns: &[Value], sign: Option<&SignOpts>) -> Result<Vec<Value>, Error> {
38    let mut out = Vec::with_capacity(turns.len());
39    let mut prev: Option<String> = None;
40    for t in turns {
41        let mut linked: Map<String, Value> = t
42            .as_object()
43            .cloned()
44            .ok_or_else(|| Error::Invalid("turn not object".into()))?;
45        if let Some(p) = &prev {
46            if !linked.contains_key("prev_hash") {
47                linked.insert("prev_hash".into(), json!(p));
48            }
49        }
50        let linked_v = Value::Object(linked);
51        let sealed = seal(&linked_v, sign)?;
52        prev = sealed
53            .get("hash")
54            .and_then(|v| v.as_str())
55            .map(String::from);
56        out.push(sealed);
57    }
58    Ok(out)
59}