Skip to main content

Module ucan

Module ucan 

Source
Expand description

Macula’s UCAN (User Controlled Authorization Networks) tokens: creation, verification, and introspection, plus the policy layer a provider gates an inbound CALL through.

Ported from macula-io/macula’s src/auth/macula_ucan_nif.erl and its native Rust NIF (native/macula_ucan_nif/src/lib.rs) — both hand-roll a JWT-shaped token (header.payload.signature, base64url-no-pad), EdDSA over Ed25519, UCAN spec version "0.10.0" (the older JWT-based draft; not the current non-JWT/IPLD UCAN 1.0 spec). Confirmed directly by reading the NIF’s own Cargo.toml: no UCAN-spec crate is depended on at all, only generic ed25519-dalek/serde_json/base64/sha2 — because no library implements 0.10.0 (the only actively maintained Rust/Go UCAN libraries target the incompatible 1.0.0-rc.1 CBOR/IPLD format, per macula-go’s own ucan package doc, which made the identical choice porting this same reference). This module does the same: hand- rolled on the crypto/serialization primitives already in this crate (ed25519-dalek via crate::identity, plus serde/serde_json/ base64 added for this module), matching the reference exactly rather than adopting an incompatible library.

A token minted here verifies against macula-go’s ucan package, the Erlang macula SDK, or vice versa — same header shape, same payload field names (iss/aud/exp/nbf/nnc/cap/fct/prf), same signing input (header_b64 + "." + payload_b64), same algorithm. Field ORDER in the JSON is not part of the compatibility contract (a verifier decodes into a struct, never re-encodes and compares bytes) — only the field NAMES and the exact bytes signed matter.

Cross-referenced against macula-go/ucan/{ucan,policy}.go, itself independently verified against this same Erlang/Rust reference earlier this session — the two ports should stay behaviorally identical.

Structs§

Capability
One entry in a UCAN token’s capability list — mirrors macula_ucan_nif’s capability() :: #{with := binary(), can := binary()}.
CreateOpts
Optional claims for create — mirrors macula_ucan_nif’s ucan_opts() map.
Payload
A UCAN token’s decoded claims — the Rust-idiomatic counterpart to WirePayload, returned from decode/verify.
Policy
What a provider requires to answer one (realm, procedure): open (any identified caller, the default) or UCAN-gated (the caller’s token must verify against required_issuer). Mirrors macula_station_link.erl’s own policy shape exactly — open | {ucan_required, Issuer} — where Issuer there is the 32-byte Ed25519 public key the gate checks the token’s signature against, not a DID string (the reference code passes it straight to macula_ucan_nif:verify/2, whose second argument is a raw public key).

Enums§

UcanError
Errors from token creation, decoding, or verification.

Functions§

compute_cid
Returns a UCAN token’s content identifier: SHA-256 of the raw token bytes, base64url-no-pad encoded. NOT a real multihash/CIDv1 — matches macula_ucan_nif:compute_cid/1’s own (loosely-named) scheme exactly. Used only for proof-chain references between UCANs (a child token’s prf entries name parent tokens by this value).
create
Mints a new UCAN token, self-issued and signed by id. issuer and audience are opaque DID strings (e.g. "did:macula:io.macula.acme") — this module does not validate or resolve DID structure, matching macula_ucan_nif:create/4,5’s own scope exactly (that’s macula_did_nif’s job on the Erlang side, out of scope here). id signs with its own Ed25519 private key; the resulting token verifies against id’s public key (KeyPair::node_id), the same convention every advertised capability in this SDK already uses.
decode
Parses a UCAN token’s payload WITHOUT verifying its signature or checking expiration. Mirrors macula_ucan_nif:decode/1 — same warning applies: never use this for an authorization decision, only verify does that.
get_audience
Decodes token (without verifying it) and returns its aud claim. Mirrors macula_ucan_nif:get_audience/1.
get_capabilities
Decodes token (without verifying it) and returns its cap claim. Mirrors macula_ucan_nif:get_capabilities/1.
get_expiration
Decodes token (without verifying it) and returns its exp claim, or None if absent. Mirrors macula_ucan_nif:get_expiration/1.
get_issuer
Decodes token (without verifying it) and returns its iss claim. Mirrors macula_ucan_nif:get_issuer/1.
get_proofs
Decodes token (without verifying it) and returns its prf claim. Mirrors macula_ucan_nif:get_proofs/1.
is_expired
Decodes token (without verifying it) and reports whether its exp claim is in the past. A token with no exp claim is never expired. Mirrors macula_ucan_nif:is_expired/1.
verify
Checks a UCAN token’s signature against public_key (the claimed issuer’s 32-byte Ed25519 public key) and its exp/nbf claims against the current time, returning the decoded payload only on full success. Mirrors macula_ucan_nif:verify/2 exactly, including its check ORDER — public key shape, then token shape, then exp, then nbf, then signature — matching both the Erlang fallback and the Rust NIF, which check claims before the signature; this module preserves that order for parity even though it means an invalid-but-well-formed token’s expiry is observable before its signature is checked.