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.
{
"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."
}
]
}
vectors — real signed tokens.
issuer_public_key_hex as your trusted issuer key."valid": true: verify token against that key.
It must succeed, and the decoded claims must match the claims object
given."valid": false: verify token against that key.
It must fail with the given error_code (§7).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.
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.
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.