You asked which goes first: the must-fail fixtures or the evidence-carrying trust response.
Both shipped. Here's where we landed.
Fixtures first (as you recommended). We now have 36 test vectors committed to the repo at vectors/ on GitHub:
- 8 positive vectors (ATC v3, JWT RS256/ES256/EdDSA, W3C VC, PoP, receipt, CRL)
- 17 negative vectors (tampered sig, tampered payload, expired, revoked, wrong domain, JWT alg=none, JWT HS256, VC wrong key, VC wrong proof type, PoP wrong nonce, PoP expired, receipt tampered evidence_hash, malformed sig, wrong version, revoked via CRL, revoked via Bitstring)
- 5 mutation vectors (single-byte flips at byte 0, middle, last of ATC v3 canonical bytes; JWT EdDSA middle byte; W3C VC middle byte)
- 6 cross-language vectors (flat object, nested arrays, Unicode keys with CJK + emoji, number edge cases, empty collections, special escapes including forward-slash)
Every vector records its canonical JCS bytes and SHA-256 hash. A Python verifier (using the cryptography library, same approach you took) runs all 36 and passes 29/29.
Your specific concern about the nested-object bug: vector neg-002-atc-tampered-payload mutates subject.agent_id (a nested field) and requires verify to return false. The mutation vectors flip single bytes in the canonical serialization. The property-based tests (23 properties, 200 random iterations each) cover idempotency, determinism, order independence, and round-trip.
Evidence-carrying trust response also shipped. The TrustGateway generates a signed ActionReceipt for every ALLOW decision. The receipt contains args_hash (SHA-256 of JCS-canonicalized arguments), evidence_hash, verification_stages array, trust_score, and an Ed25519 signature over UTA-TRUST-DECISION domain.
A caller can re-run the policy locally: take the credential + args, run verifyCredential(), compare the resulting stages against the receipt. If they disagree, the caller knows which stage diverged.
The Merkle audit log chains receipts into a tamper-evident tree. The root is signed with Ed25519 and can be published externally. Any tampering with a past receipt changes the root.
On ca_key_id: every ATC v3 signature now carries key_id. The TrustRegistry maps key_ids to public keys. The composite revocation checker tries CRL, OCSP, and Bitstring Status List based on the credential declared method.
Your verifier would now pass against the v3 vectors. We genuinely want you to run it against the new fixture set.
This article was originally published by DEV Community and written by Edison Flores.
Read original article on DEV Community