canaad has two layers:
-
Default-profile layer — validates the standard field set (
v,tenant,resource,purpose, optionaltsandx_*extensions), then produces an RFC 8785 canonical byte string. Usecanonicalize_default/canonicalizeDefaultin your application code. -
Generic-object layer — applies core rules only (size limit, duplicate-key rejection, object assertion, JCS canonicalization) without requiring any specific fields. Use
canonicalize_object/canonicalizeObjectwhen building custom profiles on top of canaad.
// the same context always produces the same bytes
let aad = AadContext::new("acme", "/doc/123", "encrypt")?
.canonicalize()?; // → Vec<u8>, pass as AAD to your ciphercanaad ships as a Rust crate, CLI, and WASM package — all produce identical output for the same input.
| Artifact | Platform | |
|---|---|---|
| canaad-core | Rust | canaad-core = "1.0" in Cargo.toml |
| canaad-cli | CLI | see below |
| canaad-wasm | Browser / Worker | npm install @gnufoo/canaad |
[dependencies]
canaad-core = "1.0"curl -fsSL https://releases.gnu.foo/canaad/install.sh | sh
# or: cargo install canaad-clinpm install @gnufoo/canaaduse canaad_core::{AadContext, canonicalize_default, canonicalize_object};
// Default profile: validates v, tenant, resource, purpose
let aad = canonicalize_default(r#"{"v":1,"tenant":"acme","resource":"/doc/123","purpose":"encrypt"}"#)?;
// Or build from scratch (timestamp is optional)
let aad = AadContext::new("acme", "/doc/123", "encrypt")?
.with_timestamp(1700000000)?
.canonicalize()?;
// Generic object: core rules only, no required fields
let aad = canonicalize_object(r#"{"z":"last","a":"first"}"#)?;echo '{"v":1,"tenant":"acme","resource":"/doc/123","purpose":"encrypt"}' | canaad canonicalize
canaad validate input.json
canaad hash input.json -o heximport init, { AadBuilder, canonicalizeDefault, canonicalizeObject } from '@gnufoo/canaad';
await init(); // call once at startup
// Default profile
const aad = new AadBuilder()
.tenant("acme")
.resource("/doc/123")
.purpose("encrypt")
.timestamp(1700000000) // optional
.build(); // → Uint8Array — pass directly as AAD to your AEAD call
// Or canonicalize existing JSON with the default profile
const aad2 = canonicalizeDefault('{"v":1,"tenant":"acme","resource":"/doc/123","purpose":"encrypt"}');
// Generic object: core rules only, no required fields
const aad3 = canonicalizeObject('{"z":"last","a":"first"}');Numbers only — no BigInt.
build()throws on NaN, Infinity, negative, or fractional values.
Prerequisites: Rust 1.70+, just,
wasm-pack (WASM only),
Rust nightly + miri component (miri only).
just ci # full check suite (mirrors CI): lint, test, audit, msrv
just ci-local # run CI workflow locally via act (requires Docker)
just test # native tests only
just build-wasm # rebuild pkg/ from crates/canaad-wasm
just miri # memory safety (nightly)MIT OR Apache-2.0