swarmauth

SwarmAuth JCT test vectors

vectors.json gives byte-exact tokens (and the pure-data temporal cases alongside them) that any independent implementation of the JCT format – Python, TypeScript, Rust, Go, anything – can check itself against without needing to ask this repo anything further. It is generated by scripts/generate_test_vectors.py and enforced against the reference Python SDK on every test run by tests/test_spec_vectors.py.

See SPEC.md §3 for the claims schema and canonicalization rules, and §7 for the error codes referenced below.

File shape

{
  "spec_version": "0.1.0-draft",
  "issuer_private_key_seed_hex": "...",  // 32-byte Ed25519 seed, hex. NON-SECRET, see warning below.
  "issuer_public_key_hex": "...",        // 32-byte Ed25519 public key, hex. Derived from the seed.
  "vectors": [
    {
      "name": "minimal_valid",
      "valid": true,
      "description": "...",
      "header": { "alg": "EdDSA", "typ": "JCT", "kid": "..." },
      "claims": { "iss": "...", "sub": "...", "capabilities": [...], "constraints": {...}, "iat": ..., "exp": ..., "jti": "..." },
      "signing_input": "<header_b64>.<payload_b64>",  // exactly what gets Ed25519-signed
      "token": "<header_b64>.<payload_b64>.<sig_b64>"
    },
    {
      "name": "ttl_exceeds_max",
      "valid": false,
      "error_code": "TOKEN_EXPIRED",
      // ... same shape, plus "error_code" instead of nothing
    }
  ],
  "temporal_test_cases": [
    {
      "name": "not_yet_valid",
      "iat": 1000, "exp": 1060, "leeway_seconds": 2, "now": 997,
      "expect_valid": false,
      "error_code": "TOKEN_NOT_YET_VALID",
      "reason": "now (997) < iat (1000) - leeway (2) == 998."
    }
  ]
}

How to use these

vectors — real signed tokens.

  1. Load issuer_public_key_hex as your trusted issuer key.
  2. For each vector with "valid": true: verify token against that key. It must succeed, and the decoded claims must match the claims object given.
  3. For each vector with "valid": false: verify token against that key. It must fail with the given error_code (§7).
  4. If your implementation issues tokens too: reconstruct token from claims and the seed in issuer_private_key_seed_hex, following §3.4’s canonicalization rule (json.dumps(obj, sort_keys=True, separators=(",", ":"))-equivalent — sorted keys, no whitespace). The result must be byte-identical to signing_input and, after signing, to token itself.

A note on freshness: iat/exp in vectors are fixed (for byte reproducibility), which means any vector marked "valid": true will eventually — and, given the 300-second TTL ceiling, quite quickly — read as expired against your real clock. That’s expected. "valid": true here means “correct signature, schema, and canonicalization,” not “currently within its time window.” If your implementation’s verify function bundles the freshness check with everything else, pin your clock to claims.iat + 10 (or similar) while checking these vectors, the way tests/test_spec_vectors.py does via monkeypatch.

temporal_test_cases — pure data, no token or key involved. Exercises exactly the TTL-ceiling and iat/exp/leeway window logic in SPEC.md §4 steps 7–9, including the inclusive boundaries (now == iat - leeway and now == exp + leeway are both accepted, not rejected). Test your temporal-check logic directly against these, independent of signing.

Regenerating

Only the reference Python SDK’s maintainers should regenerate this file — if you’re implementing SwarmAuth elsewhere, treat it as a fixed input, not something to produce yourself:

python scripts/generate_test_vectors.py

Every vector is self-checked against the current implementation before being written; the file should never be hand-edited.

⚠️ The private key here is not a secret, but it is not yours either

issuer_private_key_seed_hex is bytes(range(32)) — deliberately unmysterious and public, chosen so anyone can regenerate it by eye. It exists solely so these vectors are independently reproducible. Never use it to sign anything outside of testing against this file.