attestix (Rust)
Offline verifier for credentials and delegation chains issued by the Attestix Python core — no Python runtime needed.
It re-implements the Attestix verification surface in pure, idiomatic Rust and
passes the shared cross-language conformance vectors byte-for-byte.
Strong fit for WebAssembly and embedded targets; the verify + canonicalize core
is unsafe-free and the Rust crate is intended to become the canonical crypto
core other language ports can FFI into.
canonicalize(&Value) -> Vec<u8>— the Attestix JCS-style canonical bytesdecode_did_key(&str) -> [u8; 32]—did:key(Ed25519) → raw public keyverify_credential(&vc, now) -> CredentialResult— W3C VC: signature + expiry + revocationverify_delegation_chain(parent, child, server_pubkey, now) -> DelegationResult— UCAN EdDSA JWT chain + capability attenuation
Install
[]
= "0.4"
(Not yet published to crates.io — see Publishing. Until then, depend on the git source:)
[]
= { = "https://github.com/VibeTensor/attestix-rs" }
Verify a credential (10 lines)
use ;
use Value;
let vc: Value = from_str?; // a full W3C VC from Attestix
let now = parse_rfc3339?;
let r = verify_credential?;
assert!;
println!;
Run the bundled example:
The canonical form is JCS-style, NOT strict RFC 8785
This is the single most error-prone part of any Attestix port. The Attestix canonical form is a practical subset of JCS with two load-bearing divergences from strict RFC 8785:
- NFC Unicode normalization — every string value and every object key is
NFC-normalized before serialization. RFC 8785 explicitly does not normalize. ("cafe" + U+0301→"café".) - Whole-number floats collapse to integers —
1.0→1. Large integers (> 2^53, e.g.9007199254740993) keep full precision.
Everything else matches JCS: keys sorted by Unicode code point, (",", ":")
separators (no whitespace), raw UTF-8 output (no \uXXXX escapes). The result is
exactly:
json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
after the recursive whole-float→int pass. Signatures are Ed25519 (RFC 8032)
over those canonical bytes; the VC proofValue is padded base64url, while
UCAN JWT signatures use the unpadded base64url JWT compact form.
See the canonical-form spec at https://attestix.io/spec/bundle/v1 and the full
conformance contract in the parent repo at
spec/verify/v1/README.md.
Conformance
The shared vectors are vendored verbatim at
testdata/vectors.json and asserted by
tests/conformance.rs:
# conformance: 7/7 vectors passed
| vector | asserts |
|---|---|
canon-001 |
byte-identical JCS-style canonicalization (NFC, codepoint sort, raw UTF-8, whole-float→int, big int) |
didkey-001 |
0xed01 ‖ raw32 base58btc → 32-byte key |
vc-valid-001 |
valid VC ⇒ verify true |
vc-tampered-001 |
flipped claim byte ⇒ signature invalid ⇒ verify false |
vc-expired-001 |
past expirationDate ⇒ verify false (signature still valid) |
ucan-chain-valid-001 |
child att ⊆ parent att ⇒ verify true |
ucan-chain-escalation-002 |
child att ⊄ parent att (admin) ⇒ verify false |
WebAssembly
The verifier is verify-only (no signing, no RNG), so it builds for the browser and embedded WASM targets:
CI builds this target on every push.
Publishing
This crate is publish-ready but not yet published. To release to crates.io:
# one-time: get a token from https://crates.io/settings/tokens
No token is bundled here; publishing is an explicit, owner-run step.
License
Apache-2.0 © 2026 VibeTensor.
Part of the Attestix project: VibeTensor/attestix (Python core + conformance vectors).