use agent_action_capsule::bundle::{
build_evidence_bundle, decode_fragment, proof_source_from_cll, verify_bundle_strict,
BuildOptions, Interval, RecordSource, StoredRecord, StrictOptions,
};
use agent_action_capsule::{canonical, verify};
use base64::Engine as _;
use cll::log::{CheckpointState, CheckpointStateStore, Entry, EntrySource, State, Timestamp};
use serde_json::Value;
use std::collections::HashMap;
use std::convert::Infallible;
use std::io::{Read, Write};
use std::process::ExitCode;
const USAGE: &str = "usage: cross_language seal|verify|bundle-produce <kind>|bundle-verify";
fn main() -> ExitCode {
let args: Vec<String> = std::env::args().skip(1).collect();
let mut input = Vec::new();
let command = match args.iter().map(String::as_str).collect::<Vec<_>>()[..] {
["bundle-produce", _] | ["bundle-verify"] | ["seal"] | ["verify"] => args[0].as_str(),
_ => {
eprintln!("{USAGE}");
return ExitCode::from(2);
}
};
if let Err(e) = std::io::stdin().read_to_end(&mut input) {
eprintln!("{e}");
return ExitCode::from(2);
}
match command {
"bundle-produce" => return bundle_produce(&args[1], &input),
"bundle-verify" => return bundle_verify(&input),
_ => {}
}
let mut capsule = match verify::decode_capsule_json(&input) {
Ok(Value::Object(map)) => map,
Ok(_) => {
eprintln!("input must be a JSON object");
return ExitCode::from(2);
}
Err(e) => {
eprintln!("{e}");
return ExitCode::from(2);
}
};
if command == "seal" {
return match canonical::compute_capsule_id(&capsule) {
Ok(id) => {
capsule.insert("capsule_id".to_string(), Value::String(id));
println!("{}", Value::Object(capsule));
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("{e}");
ExitCode::FAILURE
}
};
}
let result = verify::verify(&Value::Object(capsule), None);
if !result.ok {
for finding in &result.findings {
eprintln!("{}", finding.code);
}
return ExitCode::FAILURE;
}
if let Some(id) = result.capsule_id {
println!("{id}");
}
ExitCode::SUCCESS
}
struct FixtureBackend {
entries: Vec<Entry>,
state: State,
}
impl EntrySource for FixtureBackend {
fn scan_entries(&self, after_seq: u64, limit: usize) -> cll::log::Result<Vec<Entry>> {
Ok(self
.entries
.iter()
.filter(|entry| entry.seq > after_seq)
.take(limit)
.cloned()
.collect())
}
}
impl CheckpointStateStore for FixtureBackend {
fn load_cll(&self) -> cll::log::Result<State> {
Ok(self.state.clone())
}
fn commit_cll(&self, _: u64, _: Option<&[u8]>, _: &State) -> cll::log::Result<()> {
Err(cll::log::Error::new(
cll::log::ErrorCode::Unsupported,
"the fixture log is read-only",
))
}
}
struct FixtureRecords(HashMap<String, StoredRecord>);
impl RecordSource for FixtureRecords {
type Error = Infallible;
fn get_record(&self, id: &str) -> Result<Option<StoredRecord>, Infallible> {
Ok(self.0.get(id).cloned())
}
}
fn hex_bytes(value: &Value) -> Result<Vec<u8>, String> {
let text = value.as_str().ok_or("expected a hex string")?;
hex::decode(text).map_err(|e| e.to_string())
}
fn hex_list(value: &Value) -> Result<Vec<Vec<u8>>, String> {
value
.as_array()
.ok_or("expected a list of hex strings")?
.iter()
.map(hex_bytes)
.collect()
}
fn unsigned(value: &Value) -> Result<u64, String> {
value
.as_u64()
.ok_or_else(|| format!("expected an unsigned integer, got {value}"))
}
fn fixture_source(fixture: &Value) -> Result<(FixtureBackend, FixtureRecords), String> {
let log = &fixture["log"];
let state = &log["state"];
let checkpoint = &state["checkpoint"];
let appended_at = Timestamp::from_unix_millis(1_792_368_000_000);
let entries = hex_list(&log["entries"])?
.into_iter()
.zip(1..)
.map(|(value, seq)| Entry {
seq,
value,
appended_at,
})
.collect();
let state = State {
size: unsigned(&state["size"])?,
nodes: hex_list(&state["nodes"])?,
indexed_seq: unsigned(&state["indexed_seq"])?,
first_pending_at: None,
checkpoint: Some(CheckpointState {
bytes: hex_bytes(&checkpoint["cose"])?,
size: unsigned(&checkpoint["size"])?,
indexed_seq: unsigned(&checkpoint["indexed_seq"])?,
peaks: hex_list(&checkpoint["peaks"])?,
}),
witnesses: Vec::new(),
};
let mut records = HashMap::new();
for (id, record) in fixture["records"]
.as_object()
.ok_or("records must be an object")?
{
records.insert(
id.clone(),
StoredRecord {
capsule: hex_bytes(&record["capsule"])?,
producer_envelope: hex_bytes(&record["producer_envelope"])?,
},
);
}
Ok((FixtureBackend { entries, state }, FixtureRecords(records)))
}
fn bundle_produce(kind: &str, input: &[u8]) -> ExitCode {
let fixture: Value = match serde_json::from_slice(input) {
Ok(value) => value,
Err(e) => {
eprintln!("{e}");
return ExitCode::from(2);
}
};
let Some(build) = fixture["builds"].get(kind) else {
eprintln!("unknown bundle kind {kind:?}");
return ExitCode::from(2);
};
let built = fixture_source(&fixture).and_then(|(backend, records)| {
let interval = match &build["interval_from"] {
Value::Null => Interval::ProducerSelected,
seq => Interval::From(unsigned(seq)?),
};
let root = fixture["root"].as_str().ok_or("root must be a string")?;
let mut options = BuildOptions::new(root, interval);
options.closure_depth = unsigned(&build["closure_depth"])?;
let source = proof_source_from_cll(&backend, records).map_err(|e| e.to_string())?;
let bundle = build_evidence_bundle(&source, &options).map_err(|e| e.to_string())?;
canonical::jcs(&bundle).map_err(|e| e.to_string())
});
match built {
Ok(bytes) => match std::io::stdout().write_all(&bytes) {
Ok(()) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("{e}");
ExitCode::FAILURE
}
},
Err(e) => {
eprintln!("{e}");
ExitCode::FAILURE
}
}
}
fn bundle_verify(input: &[u8]) -> ExitCode {
let fragment = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(input.trim_ascii());
match decode_fragment(&fragment) {
Ok(bundle) => {
println!(
"{}",
verify_bundle_strict(&bundle, &StrictOptions::default()).verdict()
);
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("{e}");
ExitCode::FAILURE
}
}
}