#!/usr/bin/env python3 """Generate the Appendix B (Test Vectors) section body for draft-nennemann-act, in kramdown-rfc markdown. The vectors are produced by act.vectors.generate_vectors() using fixed Ed25519 seeds and fixed JTIs, so the output is byte-for-byte reproducible. Run this whenever the reference implementation changes: python scripts/gen_appendix_b.py > /tmp/appendix-b.md then splice the result into the draft's Appendix B. """ from __future__ import annotations import json import sys from act.token import decode_jws from act.vectors import generate_vectors def b64url(data: bytes) -> str: import base64 return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii") def fold(s: str, width: int = 64) -> str: return "\n".join(s[i:i + width] for i in range(0, len(s), width)) def jblock(obj: object) -> str: return "~~~ json\n" + json.dumps(obj, indent=2, sort_keys=False) + "\n~~~" # Clean, implementation-neutral section titles (ASCII only). TITLES = { "B.1": "Valid Phase 1 ACT: root mandate, Tier 1 (Ed25519), " "no delegation", "B.2": "Valid Phase 2 ACT: completed execution (transition " "from B.1)", "B.3": "Valid Phase 2 ACT: fan-in with two predecessor tasks", "B.4": "Valid Phase 1 ACT: delegated mandate (depth 1)", "B.5": "Valid Phase 2 ACT: delegated execution record", "B.6": "Invalid: delegation depth exceeds maximum", "B.7": "Invalid: capability escalation in delegated mandate", "B.8": "Invalid: exec_act not present in cap", "B.9": "Invalid: DAG cycle (pred references own jti)", "B.10": "Invalid: missing predecessor task in DAG", "B.11": "Invalid: tampered payload", "B.12": "Invalid: expired token", "B.13": "Invalid: audience mismatch", "B.14": "Invalid: Phase 2 record signed by wrong key", "B.15": 'Invalid: unsigned token (alg "none")', } # Expected-result text per vector id, in specification terms (no # implementation-specific exception names). EXPECTED = { "B.1": "Accept. The signature verifies under kid \"iss-key\" and " "all required Phase 1 claims are present.", "B.2": "Accept. Phase 2 record; the signature verifies under kid " "\"sub-key\" and exec_act \"read.data\" is present in cap.", "B.3": "Accept. Phase 2 fan-in; pred lists the two predecessor jti " "values from the parallel branches.", "B.4": "Accept. Delegated mandate at depth 1; the delegated cap " "(max_records=5) is a subset of the parent cap " "(max_records=10).", "B.5": "Accept. Phase 2 record for the delegated mandate; the " "signature verifies under kid \"agent-c-key\".", "B.6": "Reject. del.depth (3) exceeds del.max_depth (2).", "B.7": "Reject. The delegated cap (max_records=100) is not a subset " "of the parent cap (max_records=10); privilege reduction is " "violated. This vector is a delegation-creation check; no " "token is issued.", "B.8": "Reject. exec_act \"delete.everything\" does not match any " "cap.action.", "B.9": "Reject. pred references the record's own jti, forming a " "cycle.", "B.10": "Reject. pred references a jti that is not a previously " "verified task.", "B.11": "Reject. The signature does not verify; one byte of the " "claims segment was altered after signing.", "B.12": "Reject. exp is in the past (expired 45 minutes before the " "verification time).", "B.13": "Reject. aud does not include the verifier identity.", "B.14": "Reject. The Phase 2 record claims kid \"sub-key\" but was " "signed with the issuer's private key; the signature does " "not verify.", "B.15": "Reject. The JOSE header alg is \"none\"; unsigned tokens " "MUST NOT be accepted.", } def main() -> int: vectors, ctx = generate_vectors() out: list[str] = [] out.append("# Appendix B: Test Vectors") out.append('{:numbered="false"}') out.append("") out.append( "All vectors in this appendix are produced by the reference " "implementation using fixed Ed25519 seeds and fixed identifiers, " "and are therefore reproducible. The base timestamp (iat) is " "1772064000 (2026-02-26T00:00:00Z). Long Base64url values are " "wrapped for display only; line breaks are not part of the " "value." ) out.append("") # --- Key material --- out.append("## B.0. Key Material") out.append('{:numbered="false"}') out.append("") out.append( "Three Ed25519 key pairs are used, identified by kid. Keys are " "given in JWK {{RFC7517}} form using the Ed25519 OKP encoding " "{{RFC8037}}; \"d\" is the private seed and \"x\" is the public " "key, both Base64url-encoded. These seeds are recognizable test " "patterns and MUST NOT be used in production." ) out.append("") keymap = [ ("iss-key", ctx["iss_priv"], ctx["iss_pub"]), ("sub-key", ctx["sub_priv"], ctx["sub_pub"]), ("agent-c-key", ctx["agent_c_priv"], ctx["agent_c_pub"]), ] for kid, priv, pub in keymap: jwk = { "kty": "OKP", "crv": "Ed25519", "kid": kid, "d": b64url(priv.private_bytes_raw()), "x": b64url(pub.public_bytes_raw()), } out.append(jblock(jwk)) out.append("") # --- Per-vector rendering --- for v in vectors: out.append(f"## {v.id}. {TITLES[v.id]}") out.append('{:numbered="false"}') out.append("") decoded = None if v.compact: try: header, claims, _sig, _si = decode_jws(v.compact) decoded = (header, claims) except Exception: decoded = None if decoded is not None: header, claims = decoded out.append("JOSE Header:") out.append("") out.append(jblock(header)) out.append("") out.append("JWT Claims:") out.append("") out.append(jblock(claims)) out.append("") if v.compact: out.append("JWS Compact Serialization:") out.append("") out.append("~~~") out.append(fold(v.compact)) out.append("~~~") out.append("") out.append(f"Expected result: {EXPECTED[v.id]}") out.append("") sys.stdout.write("\n".join(out) + "\n") return 0 if __name__ == "__main__": raise SystemExit(main())