jacs-core 0.11.2

JACS portable protocol layer (no I/O, no native deps)
Documentation

jacs-core

Portable JACS protocol layer — no I/O.

jacs-core is the compile-anywhere protocol crate for JACS. It holds the cryptographic primitives, canonical JSON serializer, embedded schemas, encrypted-key envelope codec, and agreement payload helpers that both the native jacs crate and the browser-side jacs-wasm wrapper share.

What it is

  • A pure-Rust library that compiles for both native targets and wasm32-unknown-unknown.
  • The single source of truth for canonical JACS bytes — signatures and agreements produced by jacs_core::CoreAgent::sign_message round-trip through native jacs::Agent::verify_string and back.
  • The home of Ed25519DalekSigner, Pq2025Signer, the DetachedSigner trait, CoreAgent, the AES-256-GCM + Argon2id encrypted-key envelope, the embedded JSON schema set (Draft 7), and the multi-party agreement payload logic.

What it is not

  • Not a filesystem layer. No std::fs, no path resolution, no config loading.
  • Not a network layer. No DNS, no HTTP, no remote registry.
  • Not an observability layer. No env-var-driven logging, no tracing subscriber wiring, no metrics export.
  • Not a CLI or MCP server. Those live in jacs-cli and jacs-mcp, both built on jacs.

If you want the full native JACS experience (storage backends, A2A, attestation, MCP, observability), use jacs. If you want to sign or verify a JACS document in the browser, use @jacs/wasm.

Quick start

use jacs_core::{CoreAgent, SigningAlgorithm};
use serde_json::json;

let mut agent = CoreAgent::ephemeral(SigningAlgorithm::Ed25519)?;
let signed = agent.sign_message(&json!({ "hello": "world" }))?;
let outcome = agent.verify(&signed)?;
assert!(outcome.valid);

For multi-party agreements:

use jacs_core::{CoreAgent, SigningAlgorithm, agreements};
use serde_json::json;

let mut alice = CoreAgent::ephemeral(SigningAlgorithm::Ed25519)?;
let mut bob = CoreAgent::ephemeral(SigningAlgorithm::Pq2025)?;
let alice_id = alice.export_agent()["jacsId"].as_str().unwrap().to_string();
let bob_id = bob.export_agent()["jacsId"].as_str().unwrap().to_string();

let mut doc = agreements::create(
    &json!({ "topic": "merge proposal" }),
    &[alice_id.clone(), bob_id.clone()],
    Some("Approve?"),
    None,
)?;
agreements::sign(&mut alice, &mut doc, "alice")?;
agreements::sign(&mut bob, &mut doc, "bob")?;

let signers: Vec<(&str, &[u8], SigningAlgorithm)> = vec![
    (alice_id.as_str(), alice.public_key(), SigningAlgorithm::Ed25519),
    (bob_id.as_str(),   bob.public_key(),   SigningAlgorithm::Pq2025),
];
let outcome = agreements::verify(&doc, &signers)?;
assert!(outcome.all_valid);

Encrypted private-key envelopes

jacs-core::envelope reads two on-disk formats — the same two the native jacs CLI has shipped:

  1. V2 JSON envelope (current writer) — AES-256-GCM cipher, Argon2id key derivation. Default for all newly-encrypted keys. Always starts with {.
  2. Legacy raw-binary PBKDF2 envelopesalt(16) || nonce(12) || ciphertext, PBKDF2-HMAC-SHA256 @ 600k iterations with a 100k legacy fallback. Read-only — no new writes.

Reserved magic prefixes

Inputs whose first 4 bytes match the ASCII pattern ^J[A-Z]{2}[0-9]$ (for example JAA1, JAC2, JAS9) are reserved for future envelope formats — memory-hard KDF variants, post-quantum AEAD wrappers, and the like. decrypt_private_key rejects them up front with CoreError::UnsupportedAlgorithm("<prefix>") so they aren't misclassified as malformed legacy PBKDF2 noise.

Compile-target guarantees

  • cargo check -p jacs-core --target wasm32-unknown-unknown passes.
  • bash scripts/forbidden-deps.sh jacs-core wasm32-unknown-unknown is the CI gate: it fails the build if ring, tokio, reqwest, hickory-resolver, object_store, rusqlite, duckdb, surrealdb, keyring, rpassword, dirs, jacs-media, mail-parser, html5ever, or opentelemetry-otlp ever appear in jacs-core's dependency graph.

Where to go next

  • jacs — native facade. Filesystem, DNS, HTTP, MCP, CLI, storage.
  • jacs-wasm — browser bindings that wrap jacs-core with a TypeScript API.
  • PRD — full design + scope of the native/wasm split (HAI internal).

License

Apache-2.0. See LICENSE-APACHE.