use base64::{engine::general_purpose::STANDARD, Engine};
use ed25519_dalek::{Signer, SigningKey};
use serde_json::{json, Value};
use crate::canonical::canonical;
use crate::envelope::{new_envelope, pae_encode};
use crate::error::Error;
use crate::types::{validate_receipt, PAYLOAD_TYPE};
pub fn ed25519_sign(sk: &[u8; 32], message: &[u8]) -> Vec<u8> {
SigningKey::from_bytes(sk).sign(message).to_bytes().to_vec()
}
pub fn sign_agent(receipt: &Value, sk: &[u8; 32]) -> Result<Value, Error> {
validate_receipt(receipt)?;
let body = canonical(receipt)?;
let pae = pae_encode(PAYLOAD_TYPE, &body);
let sig = ed25519_sign(sk, &pae);
let mut env = new_envelope(&body);
let agent_keyid = receipt["agent"]["key_id"]
.as_str()
.ok_or_else(|| Error::Invalid("receipt.agent.key_id missing".into()))?
.to_string();
env["signatures"] = json!([{
"keyid": agent_keyid,
"sig": STANDARD.encode(&sig),
}]);
Ok(env)
}
pub fn countersign_tool(envelope: &Value, sk: &[u8; 32]) -> Result<Value, Error> {
let sigs = envelope["signatures"]
.as_array()
.ok_or_else(|| Error::Invalid("envelope.signatures missing".into()))?;
if sigs.len() != 1 {
return Err(Error::Sign(format!(
"countersign_tool expects exactly 1 existing signature, got {}",
sigs.len()
)));
}
let payload_b64 = envelope["payload"]
.as_str()
.ok_or_else(|| Error::Invalid("envelope.payload missing".into()))?;
let payload_bytes = STANDARD
.decode(payload_b64)
.map_err(|e| Error::Invalid(format!("envelope.payload base64: {e}")))?;
let receipt: Value = serde_json::from_slice(&payload_bytes)?;
validate_receipt(&receipt)?;
let canonical_bytes = canonical(&receipt)?;
if payload_bytes != canonical_bytes {
return Err(Error::Sign("envelope payload is not JCS-canonical".into()));
}
let pae = pae_encode(PAYLOAD_TYPE, &canonical_bytes);
let sig = ed25519_sign(sk, &pae);
let tool_keyid = receipt["tool"]["key_id"]
.as_str()
.ok_or_else(|| Error::Invalid("receipt.tool.key_id missing".into()))?
.to_string();
let mut out = envelope.clone();
let mut sigs_out = sigs.clone();
sigs_out.push(json!({
"keyid": tool_keyid,
"sig": STANDARD.encode(&sig),
}));
out["signatures"] = Value::Array(sigs_out);
Ok(out)
}