Research preview

Python Guide

Package API patterns, backends, error handling, and production integration notes.

Skip to content

Installation

bash
# Runtime only
pip install vortex-pqc

# Development
pip install "vortex-pqc[dev]"

# With benchmarking extras
pip install "vortex-pqc[benchmark]"

Requirements: Python ≥ 3.10, no runtime dependencies.

Imports

python
# Core KEM operations
from vortex_pqc import (
    generate_keypair,
    encapsulate,
    decapsulate,
    native_backend,
)

# Size constants
from vortex_pqc import (
    PUBLIC_KEY_BYTES,     # 800
    PRIVATE_KEY_BYTES,    # 1248
    CIPHERTEXT_BYTES,     # 768
    SHARED_SECRET_BYTES,  # 32
)

# Types
from vortex_pqc import VortexKeyPair, Ciphertext, SecurityError

# PEM encoding
from vortex_pqc import PEMKind, encode_pem, decode_pem
from vortex_pqc import write_pem_file, read_pem_file

# Benchmarking
from vortex_pqc import benchmark_throughput

Backends

BackendWhen activePerformance
vortex-pqc-native-*C extension compiled at install timeFast — production path
vortex-pqc-pure-pythonNo C compiler or build failureSlower — always correct
python
import vortex_pqc

print(vortex_pqc.native_backend())
# "vortex-pqc-native-aarch64" on Apple Silicon with C extension
# "vortex-pqc-pure-python" without compiled extension

The API is identical regardless of backend. Code never needs to branch on backend name.

Typed usage patterns

Key pair as a named tuple

python
kp: vortex_pqc.VortexKeyPair = generate_keypair()
pk: bytes = kp.public_key    # always 800 bytes
sk: bytes = kp.private_key   # always 1248 bytes

Ciphertext as a named tuple

python
ct: vortex_pqc.Ciphertext = encapsulate(pk)
wire: bytes = ct.data              # send this (768 bytes)
secret: bytes = ct.shared_secret   # keep this (32 bytes)

Error handling

python
from vortex_pqc import encapsulate, decapsulate, SecurityError

# Length validation — raises ValueError
try:
    encapsulate(b"\x00" * 100)
except ValueError as e:
    print(f"Bad input: {e}")

# Decapsulation never raises on tampered ciphertexts (implicit rejection)
ss = decapsulate(tampered_ct, sk)   # returns wrong 32 bytes, no exception

# Native backend integrity failure (rare)
try:
    decapsulate(ct, sk)
except SecurityError:
    pass

Idiomatic patterns

Context manager for ephemeral keys
python
from contextlib import contextmanager
from vortex_pqc import generate_keypair, VortexKeyPair

@contextmanager
def ephemeral_keypair():
    kp = generate_keypair()
    try:
        yield kp
    finally:
        # Best-effort zeroing (Python bytes are immutable, but drop refs)
        del kp

with ephemeral_keypair() as alice:
    ct = encapsulate(alice.public_key)
Validate-then-operate
python
from vortex_pqc import PUBLIC_KEY_BYTES, CIPHERTEXT_BYTES, encapsulate, decapsulate

def safe_encapsulate(pk: bytes):
    if len(pk) != PUBLIC_KEY_BYTES:
        raise ValueError(f"expected {PUBLIC_KEY_BYTES} bytes, got {len(pk)}")
    return encapsulate(pk)

def safe_decapsulate(ct: bytes, sk: bytes):
    if len(ct) != CIPHERTEXT_BYTES:
        raise ValueError(f"expected {CIPHERTEXT_BYTES} bytes, got {len(ct)}")
    return decapsulate(ct, sk)

Testing your integration

python
def test_vortex_round_trip():
    from vortex_pqc import generate_keypair, encapsulate, decapsulate

    kp = generate_keypair()
    ct = encapsulate(kp.public_key)
    ss = decapsulate(ct.data, kp.private_key)
    assert ss == ct.shared_secret
    assert len(ss) == 32

def test_vortex_rejects_wrong_key():
    from vortex_pqc import generate_keypair, encapsulate, decapsulate

    kp1 = generate_keypair()
    kp2 = generate_keypair()
    ct = encapsulate(kp1.public_key)
    ss_wrong = decapsulate(ct.data, kp2.private_key)
    assert ss_wrong != ct.shared_secret