Skip to main content

agent_ask/
canonical.rs

1//! RFC 8785 JCS canonical encoding + CIDv1 raw+sha256.
2//!
3//! Mirrors `src/canonical.ts` in the TS reference.
4
5use data_encoding::BASE32_NOPAD;
6use serde_json::Value;
7use sha2::{Digest, Sha256};
8
9use crate::error::Error;
10
11const CID_VERSION: u8 = 0x01;
12const CODEC_RAW: u8 = 0x55;
13const MULTIHASH_SHA256: u8 = 0x12;
14const DIGEST_LEN: u8 = 0x20;
15
16pub fn jcs(value: &Value) -> Result<Vec<u8>, Error> {
17    serde_jcs::to_string(value)
18        .map(String::into_bytes)
19        .map_err(|e| Error::Invalid(format!("jcs: {e}")))
20}
21
22/// JCS over the artifact with the top-level `sig` field removed.
23pub fn artifact_bytes_for_sig(artifact: &Value) -> Result<Vec<u8>, Error> {
24    let mut clone = artifact.clone();
25    if let Some(obj) = clone.as_object_mut() {
26        obj.remove("sig");
27    }
28    jcs(&clone)
29}
30
31pub fn compute_cid(data: &[u8]) -> String {
32    let mut hasher = Sha256::new();
33    hasher.update(data);
34    let digest: [u8; 32] = hasher.finalize().into();
35    let mut buf = Vec::with_capacity(4 + 32);
36    buf.push(CID_VERSION);
37    buf.push(CODEC_RAW);
38    buf.push(MULTIHASH_SHA256);
39    buf.push(DIGEST_LEN);
40    buf.extend_from_slice(&digest);
41    let b32 = BASE32_NOPAD.encode(&buf).to_lowercase();
42    format!("b{b32}")
43}