use agent_toolprint::{
countersign_tool, did_key_from_ed25519_pubkey, sha256_hash, sign_agent, verify, DidKeyResolver,
Plaintext, VerifyOptions,
};
use base64::{engine::general_purpose::STANDARD, Engine};
use ed25519_dalek::SigningKey;
use rand::rngs::OsRng;
use serde_json::{json, Value};
#[tokio::main]
async fn main() {
let agent_sk = SigningKey::generate(&mut OsRng);
let tool_sk = SigningKey::generate(&mut OsRng);
let agent_did = did_key_from_ed25519_pubkey(&agent_sk.verifying_key().to_bytes()).unwrap();
let tool_did = did_key_from_ed25519_pubkey(&tool_sk.verifying_key().to_bytes()).unwrap();
let args = json!({"query": "bun docs"});
let response = json!({"results": ["https://bun.sh/docs"]});
let receipt = json!({
"v": "tp/0.1",
"id": uuid::Uuid::new_v4().to_string(),
"ts": chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
"agent": {"did": agent_did, "key_id": "agent"},
"tool": {"did": tool_did, "key_id": "tool"},
"call": {"name": "search", "args_hash": sha256_hash(&args).unwrap()},
"result": {"status": "ok", "response_hash": sha256_hash(&response).unwrap()},
"nonce": STANDARD.encode([0u8; 32]),
});
let env = countersign_tool(
&sign_agent(&receipt, &agent_sk.to_bytes()).unwrap(),
&tool_sk.to_bytes(),
)
.unwrap();
println!(
"envelope signatures : {}",
env["signatures"].as_array().unwrap().len()
);
let resolver = DidKeyResolver;
let mut opts = VerifyOptions::new(&resolver);
opts.plaintext = Some(Plaintext {
args: Some(args.clone()),
response: Some(response.clone()),
});
let res = verify(&env, &opts).await;
println!(
"original : {}",
if res.ok {
"PASS".to_string()
} else {
format!("FAIL ({})", res.error.clone().unwrap_or_default())
}
);
assert!(res.ok);
let mut tampered = env.clone();
let sig0 = tampered["signatures"][0]["sig"]
.as_str()
.unwrap()
.to_string();
let first = if sig0.starts_with('A') { 'B' } else { 'A' };
let mut flipped = String::new();
flipped.push(first);
flipped.push_str(&sig0[1..]);
tampered["signatures"][0]["sig"] = Value::String(flipped);
let res_bad = verify(&tampered, &VerifyOptions::new(&resolver)).await;
println!(
"tampered : {}",
if res_bad.ok {
"PASS".to_string()
} else {
format!("FAIL ({})", res_bad.error.clone().unwrap_or_default())
}
);
assert!(!res_bad.ok);
}