---
title: "Signature test vectors"
description: "Download real public-safe Signer vectors and test authenticity separately from expiry, trust and license policy."
slug: "/docs/v1/security/test-vectors"
section: "Security"
order: 15
audience: "all"
level: "advanced"
last_updated: "2026-10-17"
version: "v1"
related: ["/docs/v1/security/signed-responses","/docs/v1/security/threat-model"]
draft: false
---
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](/docs/v1/security/signed-responses).
- Build [a complete verifier](/docs/v1/security/verify-ed25519).
- Never replace your production trust anchor with a test key.

## Vector catalogue
The [machine-readable index](/docs/vectors/index.json) lists each trusted-key filename, envelope filename and expected cryptographic result. Download [all files and the runner](/docs/downloads/security-verifiers.zip), or use the individually linked JSON below. Keys and payloads are synthetic; no private key or [activation token](/docs/v1/start/glossary#activation-token) is included.

| Case | Signature | [License](/docs/v1/start/glossary#license) permission | Input |
| --- | --- | --- | --- |
| positive | true | Active at the captured server time with the documented binding | [Envelope](/docs/vectors/positive.envelope.json) |
| integral-float | true | Active at captured time; preserves 19.0 | [Envelope](/docs/vectors/integral-float.envelope.json) |
| tampered | false | Reject | [Envelope](/docs/vectors/tampered.envelope.json) |
| wrong-key | false | Reject with the separately supplied wrong trusted key | [Envelope](/docs/vectors/wrong-key.envelope.json) |
| expired | true | Reject: expiry is in 2020 | [Envelope](/docs/vectors/expired.envelope.json) |
| unicode | true | Canonicalization test, not a complete license | [Envelope](/docs/vectors/unicode.envelope.json) |
| numbers | true | Canonicalization test, not a complete license | [Envelope](/docs/vectors/numbers.envelope.json) |
| key-order | true | Canonicalization test, not a complete license | [Envelope](/docs/vectors/key-order.envelope.json) |
| normalization | true | Two distinct Unicode names remain distinct | [Envelope](/docs/vectors/normalization.envelope.json) |
| nested | true | Lists, nulls and nested objects preserve their types/order | [Envelope](/docs/vectors/nested.envelope.json) |
| 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](/docs/vectors/positive.key.json) is the separately trusted test anchor for the captured positive response.
```json
{"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](/docs/v1/start/glossary#sandbox) [activation](/docs/v1/start/glossary#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.
```json
{"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
1. Extract the archive into a new directory.
2. Open the selected [language page](/docs/v1/security/verify-ed25519) and build its verifier.
3. Run the shared runner with --command followed by the verifier command and no input filenames.
4. Confirm PASS23 and exit 0.
5. Change one byte in a copy of a valid payload and verify refusal.
6. 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
```python
"""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](/docs/v1/start/glossary#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](/docs/v1/start/glossary#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](/docs/v1/start/glossary#customer) 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](/docs/v1/security/signed-responses) and [the threat model](/docs/v1/security/threat-model), then validate your own integration in Sandbox.

## Related
- [Verification examples](/docs/v1/security/verify-ed25519)
- [Offline mode](/docs/v1/security/offline-mode)
- [Security and disclosure](/security)
