Use synthetic, reproducible signed payloads to check whether your verifier accepts authentic bytes and rejects unsafe input.
What you will learn
- Download the public keys and envelopes.
- Interpret positive, negative and expired expectations.
- Run one repeatable self-test without network calls.
Prerequisites
- Read signed responses.
- Build a complete verifier.
- Never replace your production trust anchor with a test key.
Vector catalogue
The machine-readable index lists each trusted-key filename, envelope filename and expected cryptographic result. Download all files and the runner, or use the individually linked JSON below. Keys and payloads are synthetic; no private key or activation token is included.
| Case | Signature | License permission | Input |
|---|---|---|---|
| positive | true | Active at the captured server time with the documented binding | Envelope |
| integral-float | true | Active at captured time; preserves 19.0 | Envelope |
| tampered | false | Reject | Envelope |
| wrong-key | false | Reject with the separately supplied wrong trusted key | Envelope |
| expired | true | Reject: expiry is in 2020 | Envelope |
| unicode | true | Canonicalization test, not a complete license | Envelope |
| numbers | true | Canonicalization test, not a complete license | Envelope |
| key-order | true | Canonicalization test, not a complete license | Envelope |
| normalization | true | Two distinct Unicode names remain distinct | Envelope |
| nested | true | Lists, nulls and nested objects preserve their types/order | Envelope |
| wrong-algorithm / wrong-key-id | false | Reject untrusted envelope metadata | See index |
| short-signature / padded-signature | false | Reject malformed base64url/signature | See index |
| missing-payload / array-payload | false | Reject malformed envelope | See index |
| duplicate-property / escaped-duplicate | false | Reject ambiguous JSON names | See index |
| trailing-content / invalid-unicode / depth-limit | false | Reject malformed or excessive input | See index |
| replacement-surrogate / replacement-invalid-utf8 | false | Reject malformed input instead of silently repairing it to a valid signed replacement character | See index |
Captured public key
positive.key.json is the separately trusted test anchor for the captured positive response.
{"algorithm":"Ed25519","key_id":"ed25519_c8beae78c32584df","public_key":"HC1rqDdeN-nHyHTRmNL3s_sVfhtD6VOk6INbzSR26UA","encoding":"base64url"}
Complete captured envelope
The positive response was captured from a real isolated local Sandbox activation. Its dates describe that capture, not a currently valid purchase. The expired and stress cases were signed by the unchanged server Signer using isolated synthetic keys; they are not claimed successful API activations of expired licenses.
{"payload":{"license_id":"lic_18d0daa92fddced51a44ed14","product_id":"app_f6cc3e1e2b2db33273b3","product":"Docs fixture product","plan":"fixture-plan","status":"active","environment":"sandbox","expires_at":"2026-11-13T17:43:50+00:00","license_key_mask":"TESTDOCS-••••-••••-7CCL","starts_at":"2026-10-14T17:43:50+00:00","created_at":"2026-10-14T19:43:50+00:00","max_devices":2,"max_activations":4,"activation_count":1,"min_version":null,"max_version":null,"active_devices":1,"customer":{"id":"cus_9df14dc98f63999a6adf69c8","name":"Synthetic Example User","email":"customer@example.invalid","company":null,"country":null},"product_details":{"id":"app_f6cc3e1e2b2db33273b3","name":"Docs fixture product","platform":"desktop"},"plan_details":{"id":"plan_4de2dfbe92a4aac4b0e880ac","name":"Fixture plan","slug":"fixture-plan","billing_type":"monthly","price":19.5,"currency":"EUR","duration_days":30,"trial_days":null},"features":{"pdf_export":true},"device":{"id":"dev_af3087ec2eeb536a4f4c61a7","installation_hash":"d46f8ccf48213ab042bf83539f20aae1bd14056475471aa40b32e2565ad0ea73","name":"Synthetic local fixture","platform":"test","app_version":"1.0.0"},"server_time":"2026-10-14T17:43:50+00:00","next_validation_at":"2026-10-15T17:43:50+00:00","offline_grace_until":"2026-10-21T17:43:50+00:00"},"signature":"TvLKHsvFR2O2ip13W2XqwMjA_PyUa7Q26TiVL5LcAS7nIFGOUuuU6oto9lAhYLwwyQoWaKls5sUFWLgOzdkgAg","algorithm":"Ed25519","key_id":"ed25519_c8beae78c32584df"}
Repeatable procedure
- Extract the archive into a new directory.
- Open the selected language page and build its verifier.
- Run the shared runner with --command followed by the verifier command and no input filenames.
- Confirm PASS23 and exit 0.
- Change one byte in a copy of a valid payload and verify refusal.
- Run the expired vector and confirm signature_valid=true, then reject it in your expiry policy.
Expected result: every signature expectation matches; the runner treats a required refusal as a passing test. It does not compare a test vector's claimed result to itself without executing your verifier.
Shared test runner
"""Run every public vector using your verifier; Python 3.9+, standard library.
Run: python3 selftest.py --command python3 python/verify.py
Expected: PASS23 and exit0. A mismatched result prints FAIL and exits1.
No network, database, credentials, SDK monkey-patching or access decision.
"""
import argparse
import json
import pathlib
import subprocess
import sys
parser = argparse.ArgumentParser()
parser.add_argument('--command', nargs=argparse.REMAINDER, required=True)
args = parser.parse_args()
root = pathlib.Path(__file__).resolve().parent
vectors = json.loads((root / 'vectors/index.json').read_text())['vectors']
failures = []
for row in vectors:
command = args.command + [str(root / 'vectors' / row['key']), str(root / 'vectors' / row['envelope'])]
try:
result = subprocess.run(command, capture_output=True, text=True, timeout=30)
expected = row['signature_valid']
ok = (result.returncode == (0 if expected else 1)
and result.stdout.strip() == 'signature_valid=' + str(expected).lower()
and not result.stderr)
except (OSError, subprocess.TimeoutExpired):
ok = False
if not ok:
failures.append(row['name'])
if failures:
print('FAIL: ' + ', '.join(failures))
sys.exit(1)
print('PASS' + str(len(vectors)))
Why expiry is not a negative signature
Ed25519 proves authenticity of bytes. It does not read the clock. The expired case is deliberately signature-positive and authorization-negative. Your app must also enforce the expected product, environment, installation hash, active state, start/expiry and validation/grace deadlines. Stress cases lack complete license fields and must never grant access.
The captured installation ID is the public synthetic value p1-installation-0000000001. Semantic checks use its SHA-256 and the captured server_time; the runner itself tests only authenticity.
Expected result
All 23 cases match the index in every listed language; expired is authentic but unusable.
Common mistakes
- Expecting an authentic expired signature to fail cryptographically.
- Running a vector with another case's key file.
- Editing numbers while formatting the envelope.
Security notes
Treat all vector keys as disposable test anchors. Malformed-vector files intentionally contain invalid JSON or duplicate names; refusal is required. Never import their payloads as real customers or licenses.
Troubleshooting
| Symptom | Cause | Action |
|---|---|---|
| Positive fails | Wrong key or changed representation | Use the exact pair named in index.json |
| Expired returns true | Correct authenticity result | Apply expiry separately |
| Duplicate case cannot parse | Expected security refusal | Preserve the nonzero result |
Next steps
Read signed responses and the threat model, then validate your own integration in Sandbox.