---
title: "Python signature verification"
description: "Run the complete Python Ed25519 verifier against KDPika's real signed JSON and positive, tampered, malformed and expired test cases."
slug: "/docs/v1/security/verify-ed25519/python"
section: "Signature examples"
order: 201
audience: "all"
level: "advanced"
last_updated: "2026-10-17"
version: "v1"
related: ["/docs/v1/security/signed-responses","/docs/v1/security/test-vectors"]
draft: false
---
Verify KDPika payload authenticity in Python without losing the server's signed numeric tokens.

## What you will learn
- Build and run the complete Python verifier.
- Reject altered, ambiguous and untrusted envelopes.
- Keep signature checking separate from permission to use a [license](/docs/v1/start/glossary#license).

## Prerequisites
- Read [signed responses](/docs/v1/security/signed-responses).
- Install Python 3.9+; cryptography 46.0.3.
- Download the [complete verification source archive](/docs/downloads/security-verifiers.zip) and extract it.
- Open a terminal in the extracted security directory; all paths below are relative to it.
- Use Python 3.9+ only for the optional shared test runner, not as a dependency of this verifier.

## Install and build
```bash
python3 -m venv .venv
. .venv/bin/activate
python -m pip install cryptography==46.0.3
```
Expected result: dependencies resolve and the build exits successfully. Version commands print the installed runtime. Use a maintained runtime compatible with the minimum; these are tested compatibility floors, not claims of ongoing vendor support.

The program uses no OS-specific identity or machine hardware identifier. File paths and executable suffixes may differ on Windows; use your shell's path quoting.

## Run the positive and negative cases
```bash
python python/verify.py vectors/positive.key.json vectors/positive.envelope.json
python python/verify.py vectors/tampered.key.json vectors/tampered.envelope.json
python python/verify.py vectors/expired.key.json vectors/expired.envelope.json
python3 selftest.py --command python python/verify.py
```
Expected output for the first three commands is signature_valid=true, signature_valid=false and signature_valid=true. The tampered command exits 1 intentionally; run commands separately rather than stopping a batch at that expected refusal. The shared runner prints PASS23 and exits 0 when every expected acceptance/refusal matches.

Expired remains cryptographically authentic but must not unlock access after its expiry. This program returns signature validity only. It does not replace a complete license client, network transport, offline policy or the [official SDK](/docs/v1/start/glossary#official-sdk).

## Complete source
```python
"""Verify a raw KDPika envelope, not permission to use a license.
Python 3.9+, cryptography==46.0.3. Run: python verify.py key.json envelope.json
Output: signature_valid=true (exit 0) or signature_valid=false (exit 1).
Keep key.json separately trusted; never take it from the untrusted envelope.
"""
import base64
import hashlib
import json
import re
import sys
from pathlib import Path
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey

class Number(str):
    """Retain the original server JSON numeric token, including 19.0."""

def pairs(items):
    result = {}
    for key, value in items:
        if key in result:
            raise ValueError('Duplicate JSON property')
        result[key] = value
    return result

def parse(raw):
    if len(raw) > 1048576:
        raise ValueError('JSON exceeds 1 MiB')
    return json.loads(raw.decode('utf-8'), parse_int=Number, parse_float=Number,
                      parse_constant=lambda value: (_ for _ in ()).throw(ValueError(value)),
                      object_pairs_hook=pairs)

def quote(value):
    value.encode('utf-8', errors='strict')
    return json.dumps(value, ensure_ascii=False).replace('\u2028', '\\u2028').replace('\u2029', '\\u2029')

def canonical(value, depth=0):
    if depth > 64:
        raise ValueError('JSON exceeds 64 levels')
    if isinstance(value, Number):
        return str(value)
    if isinstance(value, dict):
        return '{' + ','.join(quote(k) + ':' + canonical(value[k], depth + 1)
                              for k in sorted(value, key=lambda k: k.encode('utf-8'))) + '}'
    if isinstance(value, list):
        return '[' + ','.join(canonical(v, depth + 1) for v in value) + ']'
    if isinstance(value, str):
        return quote(value)
    if value is None:
        return 'null'
    if isinstance(value, bool):
        return 'true' if value else 'false'
    raise ValueError('Unsupported JSON value')

def decode(value, size):
    if not isinstance(value, str) or not re.fullmatch(r'[A-Za-z0-9_-]+', value):
        raise ValueError('Invalid base64url')
    raw = base64.urlsafe_b64decode(value + '=' * (-len(value) % 4))
    if len(raw) != size or base64.urlsafe_b64encode(raw).decode().rstrip('=') != value:
        raise ValueError('Invalid base64url size or padding bits')
    return raw

def verify_envelope(trusted_key_raw, envelope_raw):
    """False on malformed data or signature failure; does not check license policy."""
    try:
        key, env = parse(trusted_key_raw), parse(envelope_raw)
        public = decode(key['public_key'], 32)
        key_id = 'ed25519_' + hashlib.sha256(public).hexdigest()[:16]
        if (key['algorithm'] != 'Ed25519' or key['encoding'] != 'base64url'
                or key['key_id'] != key_id or env['algorithm'] != 'Ed25519'
                or env['key_id'] != key_id or not isinstance(env['payload'], dict)):
            return False
        Ed25519PublicKey.from_public_bytes(public).verify(
            decode(env['signature'], 64), canonical(env['payload']).encode('utf-8'))
        return True
    except (Exception, RecursionError):
        return False

if __name__ == '__main__':
    try:
        if len(sys.argv) != 3:
            raise ValueError('Expected trusted key and envelope paths')
        valid = verify_envelope(Path(sys.argv[1]).read_bytes(), Path(sys.argv[2]).read_bytes())
    except Exception:
        valid = False
    print('signature_valid=' + str(valid).lower())
    sys.exit(0 if valid else 1)
```

## Expected result
All 23 cases pass in the shared runner, including integral floats, non-BMP key order, Unicode separators, duplicate JSON names, invalid signatures and excessive depth. The original response's numeric tokens are retained, not recalculated from binary floating-point values.

## Common mistakes
- Taking the verification key from the same untrusted message being checked.
- Treating a successful signature as proof of active access, correct installation or recent payment.
- Parsing and reserializing numbers before passing the envelope into this sample.

## Security notes
The key input must come from your trusted pin or deliberate fixed-origin HTTPS trust policy. Public vector keys are test anchors, never production configuration. This sample fails closed on malformed data; never fall back to unverified [features](/docs/v1/start/glossary#feature).

After signature success, check [product](/docs/v1/start/glossary#product), environment, installation hash, active state, start/expiry, application-version policy and signed validation/grace deadlines. See [offline mode](/docs/v1/security/offline-mode). The current official SDKs have documented canonicalization limitations; this independent verifier does not silently repair their caches or alter their public APIs.

## Troubleshooting
| Symptom | Cause | Action |
| --- | --- | --- |
| Positive case fails | Wrong trusted key, changed numeric tokens or missing dependency | Restore the exact archive/vector files and rerun the build |
| Tampered case returns false | Expected rejection | Keep the refusal; do not bypass verification |
| Expired case returns true | Authenticity and expiry are separate | Reject access in the license policy layer |
| Cannot open a file | Terminal is outside the extracted security directory | Change to that directory and retry using quoted paths |

## Next steps
Run [all test vectors](/docs/v1/security/test-vectors) and apply the [signed-response policy checks](/docs/v1/security/signed-responses).

## Related
- [Verification language index](/docs/v1/security/verify-ed25519)
- [Secure token storage](/docs/v1/security/secure-token-storage)
