PROOF OF CONCEPT · v6.0.0

Qeltrix V6

Seekable · Encrypted · Streamable

Pack any file into independently-encrypted blocks. Seek any byte without decrypting the rest. Serve live over HTTP Range Requests.

ONE BLOB
↓ seek?
decrypt all 20 GB
Traditional encryption
seek
decrypt 1 block only
Qeltrix V6 block model
!

Proof of concept only. Not for production or security-critical use without an independent cryptographic audit. The PBKDF2 salt is hardcoded — see the Security Notes page for what this means and what must change for production.

What It Does

Pack / Unpack
Encrypt any file into a .qltx container. Decrypt it back. Parallel block processing across all CPU cores.
Seek Extract
Extract any byte range from an encrypted container without decrypting the whole file.
Seek Server
HTTP server with Range Request support. Seeks and decrypts only the blocks the client needs.
Gateway
TCP encryption router. Receives raw streams, encrypts into V6 blocks in real time, forwards or reflects.
◈ Visual Overview
Interactive diagrams · Gateway animations · Range seek simulator · Cipher charts
Explore →

Install

Option A — PyPI (recommended)

bash / powershell
pip install qeltrix-v6

After installing, import the package in Python as qeltrix_v6:

python
from qeltrix_v6 import pack, unpack, seek_extract, GatewayServer, SeekServer
from qeltrix_v6.crypto import random_master_key

Option B — From Source

bash
git clone https://github.com/Qeltrix/Qeltrix-v6.git
cd Qeltrix-v6

# Linux / Mac — build C library
cd c_lib && make && cd ..

# Windows — build C library with MinGW
cd c_lib && mingw32-make && cd ..

# Install Python dependencies + package
pip install cryptography
pip install -e .

Quick Test

powershell / bash
# Pack a file
python -m qeltrix_v6 pack myfile.txt myfile.qltx --passphrase secret

# Inspect the container (no passphrase needed)
python -m qeltrix_v6 info myfile.qltx

# Unpack it back
python -m qeltrix_v6 unpack myfile.qltx recovered.txt --passphrase secret

Architecture at a Glance

Input file / TCP stream | v [ C Library ] block framing, permutation, seek math, TCP I/O | v [ Python crypto ] AES-256-GCM / ChaCha20-Poly1305 · HKDF · SHA-256 | v .qltx container [ Header | Block0 | Block1 | ... | BlockN | Footer/Index ] each block: independently encrypted + authenticated
PROOF OF CONCEPT · VISUAL OVERVIEW

How Qeltrix V6 Works

An interactive visual tour of V6's architecture, capacity, and capabilities — from block layout to gateway topologies and security model.

!

This is a proof of concept. All diagrams and benchmarks below illustrate architectural design and theoretical capacity. Not validated for production use. Key management layer has known PoC limitations — see Security Notes.

The Problem V6 Solves

Traditional encryption forces a full decrypt before you can read a single byte. V6's block model breaks that constraint entirely.

⛔ Traditional Encryption
Must decrypt ALL to read ANY
▼ seek
Cost: decrypt entire file
✅ Qeltrix V6 Block Model
B0
B1
B2
B3
B4
B5
B6
▼ seek
Cost: decrypt 1 block only

Block Anatomy — What's Inside Each Block

Each block in a .qltx container is self-contained and independently verifiable. The layout below directly reflects what _clib.py writes via make_block_prefix(), make_v6c(), and encrypt_block() in crypto.py. Click a segment to see exactly what each field contains and where it comes from in code.

Block Prefix
28B
V6-C Metadata
encrypted
Ciphertext
default 1 MB*
AEAD Tag
16B (in ciphertext)
↑ Click any segment to see its contents and the code that writes it
Block Prefix (_clib.py: make_block_prefix) V6-C Metadata (_clib.py: make_v6c + crypto.py: encrypt_v6c_metadata) Ciphertext (crypto.py: encrypt_block via CDK) AEAD Tag (appended inside ciphertext by AESGCM / ChaCha20Poly1305)

Key Derivation Hierarchy

V6 uses a dual-layer key model: a single Master Key (MK) at the root, with a unique Content Derived Key (CDK) derived per block. Compromising one CDK reveals nothing about any other block.

🔑
Passphrase / Credential
User-supplied secret
↓ PBKDF2-SHA256 (200k iter)
🗝
Master Key (MK)
32 bytes · stored encrypted in header · root of trust
↓ HKDF-SHA256 ( block_index + data_hash + salt )
🔐
CDK Block 0
Unique to B0 position + content
🔐
CDK Block 1
Unique to B1 position + content
🔐
CDK Block N
Unique to BN position + content
↓ AES-256-GCM or ChaCha20-Poly1305 AEAD
🛡
Encrypted + Authenticated Block
Tamper-evident · independently verifiable

How HTTP Range Requests Work with V6

The SeekServer maps any byte-range request to the minimum set of blocks needed, decrypts only those, and returns exact bytes. This is what enables video seeking without downloading the full file.

Byte offset → Block mapping for a 20 GB video
0 GB5 GB10 GB15 GB20 GB
↑ Click "Simulate Seek" to watch a range request at byte 16,384,000,000 (≈16 GB)
Blocks decrypted: — Data read from disk: —

Gateway Topologies

The GatewayServer supports three deployment modes. Select a mode below to see its data flow.

Loopback encryption testing: the client sends a raw TCP stream to the gateway. The gateway encrypts it into V6 blocks and sends the encrypted stream back to the same client — the V6 output never leaves to any external destination. Useful for benchmarking V6 overhead in isolation.
Client App
① raw TCP stream →
raw
V6
← ② V6 encrypted stream back
GatewayServer
Encrypt only
⚠ The V6 output is returned to the Client App — not forwarded anywhere. No dest-host. No external connection.
Use case: local testing · encryption benchmarks · round-trip latency measurement

Cipher Performance: AES-256-GCM vs ChaCha20-Poly1305

V6 offers two AEAD ciphers. The right choice depends on your hardware. Both provide authenticated encryption — the performance profile differs by CPU capability.

AES-256-GCM
Hardware Accelerated
Speed (AES-NI CPU)
~4–10 GB/s
Speed (no AES-NI)
~300 MB/s
Auth tag size
16 bytes
✅ Best for: modern desktops, servers, cloud VMs
ChaCha20-Poly1305
Software Optimised
Speed (any CPU)
~2 GB/s
Speed (mobile/IoT)
~400–800 MB/s
Auth tag size
16 bytes
✅ Best for: IoT, mobile, older ARM devices

Concurrent Architecture & Scalability

Both the packer and the GatewayServer use ThreadPoolExecutor. Encryption throughput scales roughly linearly with CPU core count up to the pool limit.

Simulated throughput scaling (relative, illustrative)
Each worker maintains its own block counter, CDK chain, and cipher state — fully isolated at the cryptographic level.

What V6 Can & Cannot Do (PoC Status)

This matrix shows V6's capabilities and clearly marks what is production-ready vs. what is a known PoC limitation.

Capability Component Status Notes
Block encryption (AEAD) crypto.py ✓ Production-quality AES-256-GCM or ChaCha20-Poly1305
Per-block CDK derivation crypto.py ✓ Production-quality HKDF-SHA256 with block index + content hash
Tamper detection (AEAD tag) All blocks ✓ Production-quality Any modification fails authentication
HTTP Range Request seek SeekServer ✓ Production-quality VFS index maps byte ranges to blocks
Parallel block packing pack() ✓ Production-quality ThreadPoolExecutor, scales with cores
TCP encryption gateway GatewayServer ✓ Production-quality All 3 topologies: reflect, router, chained
MK never written to disk All ✓ Production-quality In-memory only; header stores encrypted copy
PBKDF2 salt (passphrase→MK) cli.py ⚠ PoC — hardcoded Fixed b"QeltrixV6Salt"; enables rainbow tables
MK distribution between nodes None ⚠ Not in V6 scope V6 is a file/stream encryption engine, not a key-exchange protocol. MK distribution is intentionally out of scope — build it around V6 using ECDH (X25519), RSA-OAEP, or a KMS. The master_key bytes are the only contract. You can also swap symmetric MK for an asymmetric scheme by editing crypto.py: random_master_key() and the wrap/unwrap logic.
Key rotation / re-encryption None ⚠ Not in V6 scope Re-pack with a new MK — V6 blocks are independently encrypted so you can re-encrypt selectively. A rotation wrapper can be built around pack() / unpack() without modifying V6 itself. To use asymmetric MK, modify derive_cdk() in crypto.py.
TLS on SeekServer SeekServer ⚠ HTTP only Use nginx/Caddy reverse proxy for HTTPS
Client authentication SeekServer ⚠ None Add bearer token or mTLS at reverse proxy
Write-only vs read-only key roles None ⚠ Not implemented Symmetric MK: any key holder can encrypt AND decrypt

Primary Use Cases

V6's architecture makes it a natural fit for several distinct application domains. The chart below reflects the breadth of scenarios the system is designed to address.

Full End-to-End Pipeline

In a complete deployment: a GatewayServer encrypts data at the source, stores V6 containers in untrusted storage, and a SeekServer decrypts on demand for consumers — all without the storage layer ever seeing plaintext.

Production Side (Trusted)
App / Sensor / Camera
↓ raw
V6 Gateway
Encrypt
MK: in memory only
Untrusted Storage / Network
.qltx
.qltx
.qltx
Sees only ciphertext — cannot read data
Consumer Side (Trusted)
SeekServer
Decrypt + Serve Range
MK: in memory only
↓ plaintext range
Media Player / Browser / App
CONCEPTS

Core Concepts

Understand the block model, key hierarchy, and what the MK is before diving into usage.

Block Architecture

Every file is split into fixed-size blocks (default 1 MB). Each block is encrypted independently with its own derived key. This is what enables seeking — you only need to decrypt the blocks that contain your requested byte range.

Traditional encryption: +-----------------------------------------------+ | ONE BLOB — decrypt all or nothing | +-----------------------------------------------+ Qeltrix V6: +---------+---------+---------+---------+---------+ | Header | Block 0 | Block 1 | Block 2 | Footer | | MK info | V6-C+data| V6-C+data| V6-C+data| Index | +---------+---------+---------+---------+---------+ ^ each block has own IV, salt, CDK, AEAD tag

Key Hierarchy

Passphrase | v PBKDF2-HMAC-SHA256 (200,000 iterations) Master Key (MK) — 32 bytes, lives in memory only, never written to disk plaintext | v HKDF-SHA256(MK + block_index + SHA256(block_data) + random_salt) Content Derived Key (CDK) — unique per block AND per position | v AES-256-GCM or ChaCha20-Poly1305 Encrypted Block — ciphertext + 16-byte AEAD auth tag

What is the V6-C Metadata?

Each block contains an encrypted metadata struct called V6-C. It holds the IV, salt, SHA-256 hash of the original data, and the wrapped CDK. The V6-C itself is encrypted with the Master Key using AES-256-GCM. This means even the internal structure of the container is hidden.

The Master Key (MK)

What it is

  • 32-byte symmetric secret
  • Lives in process memory only
  • Never written to disk in plaintext
  • Container stores it encrypted (wrapped)
  • Required for every operation

What V6 does NOT do

  • Transmit MK over the wire
  • Negotiate MK between nodes
  • Distribute MK to clients
  • Manage key rotation
i

The MK must be shared externally via ECDH, RSA encapsulation, or a KMS. V6 is an encryption engine — key distribution is the operator's responsibility. For local testing, passing the same --passphrase on both sides is sufficient.

Cipher Options

FlagCipherBest for
--cipher aesAES-256-GCMDefault. Hardware-accelerated (AES-NI) on most CPUs since 2010.
--cipher chachaChaCha20-Poly1305Mobile / IoT — fast in software where AES-NI is absent.
CLI · PACK / UNPACK

Pack & Unpack

Encrypt a file into a .qltx container, or decrypt a container back to the original file.

Pack a File

powershell / bash
python -m qeltrix_v6 pack input.txt output.qltx --passphrase mysecretpassword

# With options:
python -m qeltrix_v6 pack input.bin output.qltx \
    --passphrase mysecretpassword \
    --cipher chacha \
    --block-size 512 \
    --workers 8
FlagDefaultDescription
--passphraserequiredEncryption passphrase. Derives the 32-byte Master Key via PBKDF2.
--cipheraesaes = AES-256-GCM   chacha = ChaCha20-Poly1305
--block-size1024Block size in KB. Larger = fewer blocks, smaller seek granularity.
--workers4Parallel threads for block encryption.

Expected Output

output
Qeltrix V6 Pack
  Input:      myfile.c (5,189 bytes)
  Output:     myfile.qltx
  Cipher:     AES-256-GCM
  Block size: 1,048,576 bytes
  Workers:    4

[INFO] Packing 5,189 bytes -> 1 blocks
  [████████████████████] 100% (1/1 blocks)

✓ Done in 0.047s
  Blocks:     1
  Original:   5,189 bytes
  Container:  2,047 bytes (0.39x overhead)
  Speed:      0.1 MB/s
i

Small files compress well (zlib deflate is applied before encryption), which is why the container can be smaller than the original for text/code files. Binary or already-compressed files will be larger due to encryption overhead.

Unpack a Container

powershell / bash
python -m qeltrix_v6 unpack output.qltx recovered.txt --passphrase mysecretpassword

# Skip integrity checks for speed (not recommended):
python -m qeltrix_v6 unpack output.qltx recovered.txt \
    --passphrase mysecretpassword \
    --no-verify

Expected Output

output
Qeltrix V6 Unpack
  Container: myfile.qltx (2,047 bytes)
  Output:    recovered.txt
  Verify:    Yes
  Workers:   4

[INFO] Unpacking 1 blocks -> 5,189 bytes
  [████████████████████] 100% (1/1 blocks)

✓ Done in 0.000s
  Blocks:    1
  Original:  5,189 bytes
  Speed:     0.0 MB/s
  Integrity: ✓ OK

Integrity: OK means every block's SHA-256 hash and AEAD authentication tag verified successfully. Any tampering with the container bytes causes this to fail with an error before any data is written.

CLI · SEEK EXTRACT

Seek Extract

Extract any byte range from an encrypted container. Only the minimum necessary blocks are decrypted — everything else stays encrypted and untouched.

Basic Seek

powershell / bash
# Extract bytes 0–99 (first 100 bytes)
python -m qeltrix_v6 seek myfile.qltx \
    --offset 0 --length 100 \
    --passphrase mysecretpassword \
    --output chunk.bin

# Extract from the middle — bytes 5000–5499
python -m qeltrix_v6 seek myfile.qltx \
    --offset 5000 --length 500 \
    --passphrase mysecretpassword \
    --output middle.bin

# Print hex if no --output specified
python -m qeltrix_v6 seek myfile.qltx \
    --offset 0 --length 64 \
    --passphrase mysecretpassword

How Seek Works Internally

seek_extract(container, offset=5000, length=500, master_key) | v 1. Read VFS index from container footer | v 2. qltx_seek_blocks(offset=5000, len=500) -> [block_index_list] "which blocks contain bytes 5000-5499?" | v 3. Read ONLY those blocks from disk (if 1MB blocks and offset=5000, only Block 0 is needed) | v 4. Decrypt those blocks (parallel via ThreadPoolExecutor) | v 5. Slice out exactly bytes 5000-5499 from decrypted data | v Return 500 bytes — the rest of the file never touched
i

For a 10 GB video file with 1 MB blocks (~10,000 blocks), seeking to minute 45 of the video decrypts only 1–2 blocks out of 10,000. This is the core performance feature of V6.

Flags

FlagRequiredDescription
--offsetyesStart byte position in the original (pre-encryption) file.
--lengthyesNumber of bytes to extract.
--passphraseyesMust match the passphrase used during pack.
--outputnoFile to save extracted bytes. If omitted, prints hex of first 64 bytes.
--no-verifynoSkip SHA-256 integrity check per block (faster).
CLI · SEEK SERVER

Seek Server

An HTTP server that wraps a .qltx container and serves its decrypted content to any HTTP client. Supports HTTP Range Requests for partial content. Acts as an efficient encrypted file server — it decrypts on demand and sends plaintext to clients.

!

The SeekServer sends decrypted (plaintext) data to clients. It decrypts blocks server-side and serves the original bytes over HTTP. Anyone who can reach the server's port gets the plaintext. Secure the port with SSL/TLS (reverse proxy like nginx + HTTPS) or restrict it to localhost/VPN for production use.

Start the Server

powershell / bash
python -m qeltrix_v6 serve myfile.qltx --port 7621 --passphrase mysecretpassword

# All options:
python -m qeltrix_v6 serve myfile.qltx \
    --host 0.0.0.0 \
    --port 7621 \
    --passphrase mysecretpassword \
    --workers 4

Client: Full File Download

powershell
# Download the complete decrypted file
Invoke-WebRequest -Uri http://localhost:7621/ -OutFile received_file.txt

# Verify it matches original
Get-Content received_file.txt

Client: HTTP Range Requests (Partial Seek)

powershell
# Get first 100 bytes only
Invoke-WebRequest -Uri http://localhost:7621/ `
    -Headers @{ Range = "bytes=0-99" } `
    -OutFile first100.bin

# Get bytes 500 to 999
Invoke-WebRequest -Uri http://localhost:7621/ `
    -Headers @{ Range = "bytes=500-999" } `
    -OutFile middle.bin

# Get last 200 bytes
Invoke-WebRequest -Uri http://localhost:7621/ `
    -Headers @{ Range = "bytes=-200" } `
    -OutFile last200.bin
curl (Linux/Mac or curl.exe on Windows)
# Full download
curl http://localhost:7621/ -o received.txt

# Range request
curl http://localhost:7621/ -H "Range: bytes=0-99" -o chunk.bin

What Happens on Each Request

Client: GET / HTTP/1.1 Range: bytes=500-999 | v SeekServer receives request 1. Parse Range header -> seek_start=500, seek_end=999 2. Consult VFS index -> which blocks cover bytes 500-999? 3. Read only those blocks from .qltx file on disk 4. Decrypt only those blocks (MK used here, server-side) 5. Slice out exactly bytes 500-999 | v HTTP/1.1 206 Partial Content Content-Range: bytes 500-999/[total_size] Content-Length: 500 [500 bytes of decrypted plaintext] Note: client receives PLAINTEXT. Encryption is server-side only. Secure the HTTP connection with SSL if data is sensitive.

Making It Secure: Reverse Proxy with HTTPS

Because the SeekServer sends decrypted data, putting it behind an nginx reverse proxy with TLS turns it into a proper secure file server.

nginx config snippet
server {
    listen 443 ssl;
    server_name files.yourdomain.com;

    ssl_certificate     /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    location / {
        proxy_pass http://127.0.0.1:7621;
        proxy_set_header Range $http_range;
    }
}

Now clients connect over HTTPS, get TLS encryption in transit, and the SeekServer handles the V6 decryption on the backend. The .qltx file on disk stays encrypted at rest.

CLI · GATEWAY

Gateway Server

A TCP encryption router. Any raw byte stream sent to it is encrypted into V6 blocks in real time and either reflected back or forwarded to a destination. No application changes required.

Mode 1: Reflect (Testing)

Gateway encrypts incoming data and sends the V6 stream back to the same sender. Good for testing the round-trip.

terminal 1 — start gateway
python -m qeltrix_v6 gateway --port 7620 --passphrase mysecretpassword
terminal 2 — send data and read back encrypted stream
$bytes = [System.IO.File]::ReadAllBytes("$PWD\secret.txt")
$client = New-Object System.Net.Sockets.TcpClient("127.0.0.1", 7620)
$stream = $client.GetStream()
$stream.Write($bytes, 0, $bytes.Length)
$stream.Flush()

# Signal end-of-send so gateway flushes and responds
$client.Client.Shutdown([System.Net.Sockets.SocketShutdown]::Send)

# Read back the encrypted V6 stream
$ms = New-Object System.IO.MemoryStream
$buf = New-Object byte[] 4096
do { $r = $stream.Read($buf,0,$buf.Length); if($r -gt 0){$ms.Write($buf,0,$r)} } while ($r -gt 0)
$client.Close()

[System.IO.File]::WriteAllBytes("$PWD\encrypted.qltx", $ms.ToArray())
python -m qeltrix_v6 unpack encrypted.qltx recovered.txt --passphrase mysecretpassword

Mode 2: Router (Forward to Destination)

Gateway encrypts incoming plaintext and forwards the V6 stream to a separate receiver. This is the real gateway use-case — plaintext never reaches the network.

1

Start the receiver (Terminal 1)

Listens on port 7622, saves whatever arrives as a .qltx file.

powershell
$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Any, 7622)
$listener.Start()
Write-Host "Receiver ready on :7622"
$conn = $listener.AcceptTcpClient()
$s = $conn.GetStream()
$ms = New-Object System.IO.MemoryStream
$buf = New-Object byte[] 4096
do { $r = $s.Read($buf,0,$buf.Length); if($r -gt 0){$ms.Write($buf,0,$r)} } while ($r -gt 0)
$conn.Close(); $listener.Stop()
[System.IO.File]::WriteAllBytes("$PWD\received.qltx", $ms.ToArray())
Write-Host "Saved $($ms.Length) bytes"
2

Start the gateway in router mode (Terminal 2)

powershell
python -m qeltrix_v6 gateway --port 7620 --passphrase mysecretpassword ^
    --dest-host 127.0.0.1 --dest-port 7622
3

Send plaintext through the gateway (Terminal 3)

powershell
$bytes = [System.IO.File]::ReadAllBytes("$PWD\secret.txt")
$client = New-Object System.Net.Sockets.TcpClient("127.0.0.1", 7620)
$s = $client.GetStream()
$s.Write($bytes, 0, $bytes.Length)
$s.Flush()
$client.Client.Shutdown([System.Net.Sockets.SocketShutdown]::Send)
Start-Sleep -Milliseconds 500
$client.Close()
Write-Host "Sent $($bytes.Length) bytes"
4

Decrypt at the receiver (Terminal 1)

powershell
python -m qeltrix_v6 info received.qltx
python -m qeltrix_v6 unpack received.qltx final.txt --passphrase mysecretpassword
Get-Content final.txt

What the Network Sees

Sender (Terminal 3) Gateway (Terminal 2) Receiver (Terminal 1) secret.txt received.qltx [PLAINTEXT] [ENCRYPTED V6] "CONFIDENTIAL encrypt QLTX...garbage... Employee: John into V6 ...unreadable... Salary: 95000" ---------> blocks ---------> QLTXFOOT... :7620 :7622 Anyone sniffing port 7622 traffic sees only V6 ciphertext. Decryption requires the passphrase, which is never on the wire.

Gateway Stats

The gateway prints stats every 5 seconds while running:

output
Stats: 1 connections, 118 bytes in, 669 bytes out

bytes in = raw plaintext received. bytes out = encrypted V6 stream sent. The ratio (~5.7x here) reflects encryption overhead: block headers, V6-C metadata, AEAD tags, and the container footer/index.

CLI · INFO

Info Command

Inspect a .qltx container's metadata without a passphrase. Shows structure, block count, cipher, sizes, and overhead.

Usage

powershell / bash
python -m qeltrix_v6 info myfile.qltx

Example Output

output
Qeltrix V6 Container Info
  File:           myfile.qltx
  File size:      669 bytes
  Version:        6
  Flags:          0x02
  Cipher:         AES256-GCM
  Block size:     1,048,576 bytes (1024 KB)
  Total blocks:   1
  Original size:  118 bytes
  Footer offset:  493
  Footer size:    176 bytes
  Overhead:       466.9%
FieldMeaning
Flags: 0x02FLAG_STREAMING set — container was created by the gateway (stream mode) rather than pack.
Footer offsetByte position of the VFS index in the container. Used by SeekServer to find blocks fast.
Footer sizeSize of the index. Grows with block count: 1 block = 176 bytes, 1000 blocks ≈ 128 KB.
OverheadFor tiny files, overhead is huge because each block carries fixed-size headers. For large files (100 MB+) overhead drops below 1%.
PYTHON API · PACK / UNPACK

pack() & unpack()

Use Qeltrix V6 directly from Python code. Import the package and call pack / unpack with a master key.

Import

python
from qeltrix_v6 import (
    pack, unpack,
    CIPHER_AES256_GCM, CIPHER_CHACHA20_POLY1305
)
from qeltrix_v6.crypto import random_master_key

pack()

python
# Generate a random 32-byte master key
mk = random_master_key()

# Pack a file
result = pack(
    "input.txt",          # source file path
    "output.qltx",        # destination container path
    mk,                    # 32-byte master key
    block_size=1 << 20,    # 1 MB blocks (default)
    cipher_id=CIPHER_AES256_GCM,
    workers=4,
    progress_cb=lambda done, total: print(f"{done}/{total}")
)

print(result.total_blocks)    # int
print(result.original_size)   # int bytes
print(result.elapsed_s)       # float seconds

unpack()

python
result = unpack(
    "output.qltx",    # container path
    "recovered.txt",  # output path
    mk,                # same 32-byte master key
    no_verify=False,   # True skips integrity checks
    workers=4
)

print(result.integrity_ok)    # True = all blocks verified
print(result.original_size)   # int bytes
print(result.elapsed_s)

Pack from Bytes (no file)

python
# pack() also accepts raw bytes or a file-like object as source
data = b"Hello, encrypted world!"
result = pack(data, "output.qltx", mk)

# Or a BytesIO stream
import io
stream = io.BytesIO(b"stream data")
result = pack(stream, "output.qltx", mk)
PYTHON API · SEEK EXTRACT

seek_extract()

Programmatically extract any byte range from a V6 container. Only the required blocks are read and decrypted.

Usage

python
from qeltrix_v6 import seek_extract

# Extract 500 bytes starting at offset 1000
data = seek_extract(
    "bigfile.qltx",
    seek_offset=1000,
    seek_len=500,
    master_key=mk,
    no_verify=False,
    workers=4
)

print(len(data))   # 500
print(data[:32])   # first 32 bytes of the extracted slice

Video Streaming Example

python
# Serve a specific second of a video stored as .qltx
# Video: 30 fps, 1920x1080, ~4 MB/s bitrate

BYTES_PER_SECOND = 4_000_000
timestamp_seconds = 120  # seek to 2 minutes

chunk = seek_extract(
    "movie.qltx",
    seek_offset=timestamp_seconds * BYTES_PER_SECOND,
    seek_len=BYTES_PER_SECOND,   # 1 second of video
    master_key=mk
)
# chunk contains exactly 1 second of decrypted video data
# only the 1-4 blocks covering that range were decrypted

Returns

Always returns bytes. The length will be exactly seek_len unless the range exceeds the end of the file, in which case it returns what's available.

PYTHON API · GATEWAYSERVER

GatewayServer

Embed a V6 encryption gateway directly in your Python application. Accepts raw TCP connections and produces encrypted V6 streams.

Basic Usage

python
from qeltrix_v6 import GatewayServer, CIPHER_AES256_GCM
from qeltrix_v6.crypto import random_master_key
import time

mk = random_master_key()

gw = GatewayServer(
    mk,
    listen_host="0.0.0.0",
    listen_port=7620,
    cipher_id=CIPHER_AES256_GCM,
    workers=4,
    dest_host=None,    # None = reflect mode
    dest_port=None,
)

gw.start(blocking=False)   # runs in background thread

# Your application runs here...
try:
    while True:
        time.sleep(5)
        s = gw.stats
        print(s['connections'], s['bytes_in'], s['bytes_out'])
except KeyboardInterrupt:
    gw.stop()

Router Mode (Forward to Destination)

python
gw = GatewayServer(
    mk,
    listen_port=7620,
    dest_host="storage-server.internal",  # forward here
    dest_port=7622,
)
gw.start(blocking=True)   # blocks until Ctrl+C

StreamPacker: Lower-Level API

python
from qeltrix_v6 import StreamPacker
import io

# Encrypt a stream to a buffer (no file I/O)
packer = StreamPacker(mk, block_size=1 << 20, cipher_id=CIPHER_AES256_GCM)

source = io.BytesIO(b"my data stream")
output = io.BytesIO()

index = packer.pack_stream(
    source,
    lambda chunk: output.write(chunk),
    total_size=None
)

encrypted_bytes = output.getvalue()
print(len(encrypted_bytes), "bytes encrypted")

HttpStreamGateway: Encrypt a URL Download

python
from qeltrix_v6 import HttpStreamGateway

# Download a URL and save it as an encrypted V6 container
gw = HttpStreamGateway(
    mk,
    url="https://example.com/data.bin",
    block_size=1 << 20,
    cipher_id=CIPHER_AES256_GCM,
)
bytes_written = gw.stream_to_file("encrypted_download.qltx")
print(bytes_written, "bytes in container")
PYTHON API · SEEKSERVER

SeekServer

Embed an HTTP seek server in your application. Exposes a .qltx container over HTTP with full Range Request support. Decrypts and serves data on demand.

Basic Usage

python
from qeltrix_v6 import SeekServer
from qeltrix_v6.crypto import random_master_key
import time

mk = random_master_key()

srv = SeekServer(
    "bigfile.qltx",    # path to .qltx container
    mk,                  # master key
    host="0.0.0.0",
    port=7621,
    workers=4
)

srv.start(blocking=False)   # background thread

# Server runs; any HTTP client can now GET / with Range headers
try:
    while True:
        time.sleep(1)
except KeyboardInterrupt:
    srv.stop()

Programmatic Seek (without HTTP)

python
# Use seek_extract directly — no HTTP server needed
from qeltrix_v6 import seek_extract

# Read bytes 0-1023 of the original file
chunk = seek_extract("bigfile.qltx", 0, 1024, mk)

# Read a "frame" at position 4096
frame = seek_extract("bigfile.qltx", 4096, 512, mk)

Full Workflow: Pack then Serve

python
from qeltrix_v6 import pack, SeekServer
from qeltrix_v6.crypto import random_master_key
import time

mk = random_master_key()

# 1. Encrypt the file
pack("video.mp4", "video.qltx", mk, block_size=1 << 20)

# 2. Serve it — any media player with the URL can now seek
srv = SeekServer("video.qltx", mk, port=7621)
srv.start(blocking=False)
print("Serving at http://localhost:7621/")

# Media player hits: GET / Range: bytes=X-Y
# SeekServer decrypts only those blocks and returns plaintext
while True: time.sleep(1)
PYTHON API · CRYPTO

Crypto API

Low-level cryptographic functions. Use these to integrate V6's key derivation and encryption into your own code.

Key Generation

python
from qeltrix_v6.crypto import (
    random_master_key, random_key, random_iv,
    random_salt, random_bytes
)

mk   = random_master_key()  # 32 bytes — root key
key  = random_key()          # 32 bytes — AES/ChaCha key
iv   = random_iv()           # 12 bytes — GCM/ChaCha nonce
salt = random_salt()         # 32 bytes — HKDF salt

Content Derived Key (CDK)

python
from qeltrix_v6.crypto import derive_cdk, sha256, CIPHER_AES256_GCM

block_data = b"raw block content"
block_hash = sha256(block_data)
salt       = random_salt()

# CDK is bound to: master key + block position + content hash + salt
cdk = derive_cdk(
    master_key=mk,
    block_index=0,
    block_data_hash=block_hash,
    salt=salt,
    cipher_id=CIPHER_AES256_GCM
)
# cdk is 32 bytes, unique to this block's content AND position

Encrypt / Decrypt a Block

python
from qeltrix_v6.crypto import encrypt_block, decrypt_block, CIPHER_AES256_GCM

plaintext  = b"secret data"
iv         = random_iv()
ciphertext = encrypt_block(plaintext, cdk, iv, CIPHER_AES256_GCM, aad=b"context")

# Decrypt
recovered = decrypt_block(ciphertext, cdk, iv, CIPHER_AES256_GCM, aad=b"context")
assert recovered == plaintext

Master Key Wrapping

python
from qeltrix_v6.crypto import wrap_master_key, unwrap_master_key

# Wrap MK with a passphrase for storage
wrapped_mk, iv, salt = wrap_master_key(mk, passphrase=b"mypassphrase")

# Recover MK
recovered_mk = unwrap_master_key(wrapped_mk, b"mypassphrase", iv, salt)
assert recovered_mk == mk

# Note: CLI uses a HARDCODED salt for PBKDF2 (PoC limitation)
# For production: generate random salt per container
PYTHON API · CONTAINER INTERNALS

Container Internals

Everything inside qeltrix_v6.container — the dataclasses returned by pack() and unpack(), module-level constants, and the private worker functions that run in the thread pool.

Module Constants

python
from qeltrix_v6.container import DEFAULT_BLOCK_SIZE, MAX_WORKERS

DEFAULT_BLOCK_SIZE = 1 << 20   # 1,048,576 bytes (1 MB) — default block size for all operations
MAX_WORKERS        = min(os.cpu_count() or 4, 16)   # thread pool cap, auto-detected at import

Dataclasses

These are returned by pack(), unpack(), and stored in the VFS footer index. All fields are read-only after construction.

BlockIndexEntry

python
from qeltrix_v6.container import BlockIndexEntry

# One entry per block in the QLTXFOOT VFS index.
# Used by seek_extract() to find exactly which blocks cover a byte range.
@dataclass
class BlockIndexEntry:
    block_index:      int    # 0-based block number
    original_offset:  int    # byte offset of this block in the ORIGINAL file
    original_size:    int    # plaintext size of this block (varies for last block)
    container_offset: int    # byte offset of this block in the .qltx CONTAINER
    container_size:   int    # total bytes this block occupies in the container
                              # (prefix 28 + v6c_iv 12 + enc_v6c N + payload)
    block_hash:       bytes  # SHA-256 of original (pre-compression) block data, 32 bytes
    iv:               bytes  # 12-byte nonce used to encrypt the block payload
    salt:             bytes  # 32-byte random HKDF salt used for CDK derivation
    cipher_id:        int    # 0 = AES-256-GCM, 1 = ChaCha20-Poly1305

V6PackResult

python
from qeltrix_v6.container import V6PackResult   # also: from qeltrix_v6 import V6PackResult

# Returned by pack()
@dataclass
class V6PackResult:
    container_path: str    # output .qltx file path
    total_blocks:   int    # number of blocks written
    original_size:  int    # original file size in bytes
    block_size:     int    # block size used (bytes)
    cipher_id:      int    # cipher used (0 or 1)
    elapsed_s:      float  # wall-clock seconds for entire pack operation

# Usage:
r = pack("file.txt", "file.qltx", mk)
print(r.total_blocks, r.original_size, r.elapsed_s)

V6UnpackResult

python
from qeltrix_v6.container import V6UnpackResult   # also: from qeltrix_v6 import V6UnpackResult

# Returned by unpack()
@dataclass
class V6UnpackResult:
    output_path:   str    # path of the recovered file
    total_blocks:  int    # number of blocks decrypted
    original_size: int    # recovered file size in bytes
    elapsed_s:     float  # wall-clock seconds
    integrity_ok:  bool   # True if all block SHA-256 hashes verified correctly
                           # False signals container tampering or wrong passphrase

# Usage:
r = unpack("file.qltx", "recovered.txt", mk)
if not r.integrity_ok:
    raise RuntimeError("Container tampered!")

Private Worker Functions

These run inside ThreadPoolExecutor and are the actual encryption/decryption engines. They are intentionally importable so the thread pool can call them cross-process safely on Windows (no lambda or closure capture issues).

_pack_one_block()

python
from qeltrix_v6.container import _pack_one_block

# Signature:
def _pack_one_block(block_index, raw_data, master_key, cipher_id) -> dict:

# Steps performed:
#  1. SHA-256(raw_data)              → block_hash
#  2. zlib.compress(raw_data, 6)     → compressed
#  3. random_salt() + random_iv()    → per-block entropy
#  4. derive_cdk(mk, idx, hash, salt) → CDK
#  5. permute_block(compressed, hash) → permuted  [C lib]
#  6. AEAD encrypt(permuted, CDK, iv, aad=pack(">Q", block_index)) → payload
#  7. wrap_cdk(CDK, mk)              → (wrapped_cdk 48b, cdk_iv 12b)
#  8. make_v6c(...)                  → v6c_raw  [C lib]
#  9. encrypt_v6c_metadata(v6c_raw, mk) → (enc_v6c, v6c_iv)

# Returns dict with keys:
# "block_index", "v6c_iv", "enc_v6c", "payload",
# "block_hash", "iv", "salt", "cipher_id", "orig_sz", "comp_sz"

_unpack_one_block()

python
from qeltrix_v6.container import _unpack_one_block

# Signature:
def _unpack_one_block(block_index, enc_v6c, v6c_iv, payload, master_key, no_verify)     -> tuple[int, bytes]:

# Steps performed (exact reverse of _pack_one_block):
#  1. decrypt_v6c_metadata(enc_v6c, mk, v6c_iv)   → v6c_raw
#  2. qltx_read_v6c(v6c_raw)  → block_hash, iv, salt, cdk_blob, cipher_id
#  3. unwrap_cdk(cdk_blob[12:60], mk, cdk_blob[:12]) → CDK
#  4. AEAD decrypt(payload, CDK, iv, aad=pack(">Q", block_index)) → permuted
#  5. unpermute_block(permuted, block_hash) → compressed
#  6. zlib.decompress(compressed) → raw_data
#  7. if not no_verify: assert SHA-256(raw_data) == block_hash

# Returns: (block_index: int, raw_data: bytes)
# Raises ValueError on bad V6-C magic or integrity failure

Footer Serialization

python
from qeltrix_v6.container import _serialize_footer, _parse_footer, _read_container_index

# Serialize list[BlockIndexEntry] → bytes  (QLTXFOOT magic + count + hash + entries)
# Layout: b"QLTXFOOT" (8) + entry_count uint64 (8) + SHA-256 of entries (32) + entries
# Each entry: 128 bytes from C struct qltx_index_entry via qltx_write_index_entry()
footer_bytes = _serialize_footer(entries)   # list[BlockIndexEntry] → bytes

# Parse QLTXFOOT bytes back into list[BlockIndexEntry]
entries = _parse_footer(footer_bytes)        # bytes → list[BlockIndexEntry]
# Raises ValueError if magic != b"QLTXFOOT" or data is truncated

# High-level: read header dict + full index from a .qltx file in one call
# Returns (header_info: dict, entries: list[BlockIndexEntry])
hdr, entries = _read_container_index("myfile.qltx")

# hdr dict keys:
# "version", "flags", "cipher_default", "block_size",
# "total_blocks", "original_size", "footer_offset", "footer_size"

# Example: check original size without decrypting anything
hdr, _ = _read_container_index("secret.qltx")
print("Original size:", hdr["original_size"], "bytes")
PYTHON API · CRYPTO (FULL)

Crypto Module — All Functions

Every function in qeltrix_v6.crypto with signatures and return types taken directly from the source.

V6-C Metadata Encryption

Every block's metadata struct (IV, salt, hash, wrapped CDK) is itself encrypted with the MK before being written to the container. These two functions handle that layer.

python
from qeltrix_v6.crypto import encrypt_v6c_metadata, decrypt_v6c_metadata

# Encrypt a V6-C metadata bytes blob with the MK
# Returns (encrypted_metadata: bytes, iv: bytes)
enc_meta, meta_iv = encrypt_v6c_metadata(v6c_raw_bytes, mk)
# Uses AES-256-GCM with aad=b"V6C-METADATA" internally

# Decrypt it back
v6c_raw = decrypt_v6c_metadata(enc_meta, mk, meta_iv)

CDK Wrapping / Unwrapping

The CDK itself is wrapped (AES-GCM encrypted) with the MK before being stored in the V6-C metadata. This is the dual-layer security: both the CDK and the metadata that contains it are protected by the MK.

python
from qeltrix_v6.crypto import wrap_cdk, unwrap_cdk

# Wrap CDK with MK — returns (wrapped_cdk: bytes, iv: bytes)
# wrapped_cdk is 48 bytes (32-byte key + 16-byte GCM tag)
wrapped_cdk, cdk_iv = wrap_cdk(cdk, mk)
# aad=b"V6C-CDK-WRAP" used internally

# Recover CDK
cdk = unwrap_cdk(wrapped_cdk, mk, cdk_iv)

Hashing

python
from qeltrix_v6.crypto import sha256, sha256_file, hash_footer_entries

# SHA-256 of bytes → 32 bytes
h = sha256(b"data")

# Streaming SHA-256 of a file (avoids loading whole file into RAM)
h = sha256_file("bigfile.bin", chunk_size=1 << 20)

# SHA-256 of all footer index entries concatenated
# Used internally to verify footer integrity
footer_hash = hash_footer_entries([entry1_bytes, entry2_bytes])

Constants

python
from qeltrix_v6.crypto import (
    CIPHER_AES256_GCM,        # = 0
    CIPHER_CHACHA20_POLY1305, # = 1
    CIPHER_NAMES,             # = {0: "AES256-GCM", 1: "ChaCha20-Poly1305"}
    AES_KEY_SIZE,             # = 32
    AES_IV_SIZE,              # = 12
    CHACHA_KEY_SIZE,          # = 32
    CHACHA_IV_SIZE,           # = 12
    SALT_SIZE,                # = 32
    MASTER_KEY_SIZE,          # = 32
)

Full Function Reference

FunctionReturnsDescription
random_bytes(n)bytesCryptographically secure random bytes via os.urandom
random_key(cipher_id)bytes 32Random AES or ChaCha key
random_iv(cipher_id)bytes 12Random 96-bit nonce
random_salt()bytes 32Random HKDF salt
random_master_key()bytes 32Random MK
sha256(data)bytes 32SHA-256 digest
sha256_file(path, chunk_size)bytes 32Streaming SHA-256 of file
derive_cdk(mk, block_index, block_hash, salt, cipher_id)bytes 32HKDF-SHA256 CDK derivation
encrypt_block(pt, key, iv, cipher_id, aad)bytesAEAD encrypt; tag appended
decrypt_block(ct, key, iv, cipher_id, aad)bytesAEAD decrypt; raises InvalidTag on tamper
encrypt_v6c_metadata(meta, mk)(bytes, bytes)Encrypt V6-C struct → (enc, iv)
decrypt_v6c_metadata(enc, mk, iv)bytesDecrypt V6-C struct
wrap_cdk(cdk, mk)(bytes 48, bytes 12)Wrap CDK with MK → (wrapped, iv)
unwrap_cdk(wrapped, mk, iv)bytes 32Recover CDK
wrap_master_key(mk, passphrase)(bytes, bytes, bytes)Wrap MK → (wrapped, iv, salt)
unwrap_master_key(wrapped, passphrase, iv, salt)bytes 32Recover MK
hash_footer_entries(entries)bytes 32SHA-256 of all index entries
PYTHON API · GATEWAY (FULL)

Gateway Module — All Classes

Complete reference for GatewayServer, StreamPacker, HttpStreamGateway, SeekServer, _SocketStream, and the flag constants from qeltrix_v6.gateway.

Flag Constants

python
from qeltrix_v6 import FLAG_NO_VERIFY, FLAG_STREAMING

FLAG_NO_VERIFY = 0x01  # Skip per-block hash checks on unpack
FLAG_STREAMING = 0x02  # Set in container header when created by gateway/StreamPacker
# Gateway always sets FLAG_STREAMING. info command shows Flags: 0x02.

GatewayServer — Full Reference

python
from qeltrix_v6 import GatewayServer, CIPHER_AES256_GCM

gw = GatewayServer(
    master_key=mk,              # 32-byte MK — required
    listen_host="0.0.0.0",      # bind address
    listen_port=7620,            # listen port
    block_size=1 << 20,          # 1 MB blocks (default)
    cipher_id=CIPHER_AES256_GCM, # or CIPHER_CHACHA20_POLY1305
    workers=4,                   # ThreadPoolExecutor workers per connection
    dest_host=None,              # None = reflect; str = forward to this host
    dest_port=None,              # forward port (required if dest_host set)
)

gw.start(blocking=False)   # blocking=True blocks thread until stop()
gw.stop()                  # sets _running=False; does a brief self-connect to unblock accept()

# Live stats dict — updated per connection
s = gw.stats
# s["connections"]  int  — total connections accepted since start
# s["bytes_in"]     int  — total raw plaintext bytes received
# s["bytes_out"]    int  — total encrypted V6 bytes sent
# s["blocks"]       int  — total V6 blocks produced

StreamPacker — Full Reference

The core of the Gateway. Reads a source stream block-by-block, encrypts each block in a thread pool, and writes a valid V6 container to a dest_write callback in real time. Used by GatewayServer internally; also usable standalone.

python
from qeltrix_v6 import StreamPacker
import io

packer = StreamPacker(
    master_key=mk,
    block_size=1 << 20,
    cipher_id=0,     # CIPHER_AES256_GCM
    workers=4,
    no_verify=False, # if True, sets FLAG_NO_VERIFY in header
)

# Stream source bytes → dest_write callback → valid .qltx bytes emitted
buf = io.BytesIO()
index_entries = packer.pack_stream(
    source=io.BytesIO(b"data to encrypt"),
    dest_write=buf.write,     # any callable(bytes) — socket.sendall, file.write, etc.
    total_size=None,           # int or None; used for progress_cb denominator
    progress_cb=None           # optional callable(blocks_done, total_blocks)
)
# Returns list[BlockIndexEntry] — the VFS index

# After pack_stream, patch the header with correct footer_offset / total_blocks
# (required when buf is seekable; skip for pure streaming sockets)
packer.patch_header(buf, total_size=15)
# Rewrites first N bytes of buf with correct header in place

# Inspect state after pack_stream:
packer._block_index      # int: total blocks written
packer._footer_offset    # int: byte offset of QLTXFOOT in container
packer._footer_size      # int: size of footer
packer._container_offset # int: current write position
packer._index            # list[BlockIndexEntry]: built index

Streaming Direct to a Socket

python
import socket, io

dest = socket.create_connection(("storage-server", 7622))
packer = StreamPacker(mk)
source = io.BytesIO(b"payload")

packer.pack_stream(source, dest.sendall)
# Note: patch_header not possible on a socket (not seekable)
# For socket destinations, header footer_offset will be 0 in PoC.
dest.close()

HttpStreamGateway — Full Reference

Downloads a URL and saves the content as a live V6 encrypted container. Streams the HTTP response directly into the encryption pipeline — no temporary files needed for the plaintext.

python
from qeltrix_v6 import HttpStreamGateway

gw = HttpStreamGateway(
    master_key=mk,
    url="https://example.com/dataset.bin",
    block_size=1 << 20,
    cipher_id=0,   # CIPHER_AES256_GCM
    workers=4,
)

# Download and save as .qltx (returns bytes written)
n = gw.stream_to_file(
    "output.qltx",
    progress_cb=lambda done, total: print(f"{done}/{total}")
)
print(n, "bytes written")

# If Content-Length header is present in HTTP response, total_size is
# passed to StreamPacker for progress reporting. Otherwise total=0.

SeekServer — Full Reference

HTTP server that wraps a .qltx file. Loads the VFS index once at startup. Each request decrypts only the blocks needed for the requested byte range and returns plaintext over HTTP.

python
from qeltrix_v6 import SeekServer

srv = SeekServer(
    container_path="bigfile.qltx",  # .qltx file path
    master_key=mk,                   # 32-byte MK
    host="0.0.0.0",
    port=7621,
    workers=4
)
# __init__ immediately calls _read_container_index() and caches:
# srv._header    — dict with version, flags, block_size, original_size, etc.
# srv._index     — list[BlockIndexEntry]
# srv._entry_map — dict[block_index -> BlockIndexEntry]

srv.start(blocking=False)  # background thread
srv.stop()                 # sets _running=False; accept() exits on next 1s timeout

# HTTP request handling:
# GET /  (no Range)  → unpack() full container to tmp file → serve 200 OK
# GET /  Range: bytes=X-Y  → seek_extract(X, Y-X+1) → serve 206 Partial Content
# GET /  Range: bytes=-N   → serve last N bytes (suffix range)

What the Server Returns

RequestResponseBody
GET / no Range header200 OKFull decrypted original file. Uses unpack() to a temp file.
GET / Range: bytes=0-999206 Partial ContentBytes 0–999 of decrypted original. Uses seek_extract(0, 1000).
GET / Range: bytes=-500206 Partial ContentLast 500 bytes. Resolved to original_size - 500.

_SocketStream — Adapter Class

Wraps a raw socket (or fd) as a Python io.RawIOBase stream so it can be passed to StreamPacker.pack_stream(). Two backends: Python socket (default, recommended) and C qltx_recv_exact (for known-length streams only).

python
from qeltrix_v6.gateway import _SocketStream

# Python backend (default) — use for all normal cases
stream = _SocketStream(sock)          # sock is a socket.socket
stream = _SocketStream(fd_int)        # int fd — wrapped via socket.fromfd()

# C backend — only for known-length streams, maximum throughput
# blocks until exactly n bytes arrive; no partial-read or EOF handling
lib = _clib.get_lib()
stream = _SocketStream(raw_fd, lib=lib, use_c=True)

# Use as a regular readable stream
packer.pack_stream(stream, dest_write)

Private Handler Methods

These are instance methods called in background threads. They are not part of the public API but are documented here for completeness and for anyone extending the gateway.

GatewayServer._handle_client()

python — internal
# Called in a daemon thread per accepted TCP connection.
# Signature: _handle_client(self, client_sock, ip: str, port: int)
#
# Flow:
#  1. Read all incoming raw bytes from client_sock (recv loop, 30s timeout)
#  2. Accumulate chunks → raw_data bytes; update stats["bytes_in"]
#  3. Build BytesIO buffer, create StreamPacker
#  4. StreamPacker.pack_stream(BytesIO(raw_data), buf.write, total_size)
#  5. StreamPacker.patch_header(buf, total_size)  — fix footer_offset in header
#  6a. If dest_host set: socket.create_connection(dest_host, dest_port)
#      sendall(container); update stats["bytes_out"]
#  6b. If no dest_host: client_sock.sendall(container)  — reflect mode
#  7. Update stats["blocks"] += packer._block_index
#  8. Close client_sock in finally block

SeekServer._handle_http()

python — internal
# Called in a daemon thread per accepted HTTP connection.
# Signature: _handle_http(self, conn: socket.socket, addr)
#
# Flow:
#  1. Recv until b"\r\n\r\n" found — accumulates full HTTP headers
#  2. Parse first line (e.g. "GET / HTTP/1.1") and header dict
#  3a. If Range header present:
#      → qltx_parse_range_header(range_val) → (seek_start, seek_end)
#      → Handle suffix range: bytes=-N  means last N bytes
#      → seek_extract(container, seek_start, seek_len, mk)
#      → Send HTTP/1.1 206 Partial Content + Content-Range header + data
#  3b. If no Range header:
#      → unpack(container, tmp_file, mk)
#      → Read tmp_file, send HTTP/1.1 200 OK + full content
#      → Delete tmp_file (tempfile.NamedTemporaryFile)
#  4. Close conn in finally block

# Response headers sent:
# 206: Content-Type, Content-Length, Content-Range, Accept-Ranges, Connection
# 200: Content-Type, Content-Length, Accept-Ranges, Connection

StreamPacker._emit_block()

python — internal
# Called by pack_stream() to write one completed block to dest_write.
# Signature: _emit_block(self, result: dict, dest_write: callable)
#
# Steps:
#  1. Unpack result dict: block_index, v6c_iv, enc_v6c, payload, hash, iv, salt, cipher_id, orig_sz
#  2. Compute v6c_size_field = len(v6c_iv) + len(enc_v6c)  = 12 + N
#  3. make_block_prefix(bidx, v6c_size_field, len(payload)) → 28-byte prefix
#  4. Concatenate: prefix + v6c_iv + enc_v6c + payload → data bytes
#  5. dest_write(data) — socket.sendall, file.write, BytesIO.write, etc.
#  6. Append BlockIndexEntry to self._index
#  7. Advance self._container_offset by len(data)
#
# Called in-order by _flush_pending() inside the ThreadPoolExecutor context.
PYTHON API · _CLIB

_clib Module — C Library Bindings

Python wrappers around every exported C function in libqeltrix_v6.so / qeltrix_v6.dll. These are the low-level building blocks used by container.py and gateway.py.

Library Loading

python
from qeltrix_v6._clib import get_lib, c_version

# Load and return the ctypes CDLL (singleton — loaded once)
lib = get_lib()

# Version string from the C library
print(c_version())   # e.g. "6.0.0"

# Library search order (auto-detected by _find_lib()):
# Windows:  qeltrix_v6.dll  next to package dir, then ../c_lib/
# Linux:    libqeltrix_v6.so  next to package dir, then ../c_lib/
# macOS:    libqeltrix_v6.dylib  next to package dir, then ../c_lib/

Block Math

python
from qeltrix_v6._clib import block_count, block_range, seek_blocks

# How many blocks does a file of file_size bytes need?
n = block_count(file_size=10_000_000, block_size=1 << 20)  # → 10

# Byte range of block N in the original file
# Returns (offset: int, size: int)
off, sz = block_range(block_index=2, file_size=10_000_000, block_size=1 << 20)
# off=2097152, sz=1048576

# Which block indices cover a byte range seek?
# Returns list[int] of block indices needed
blocks = seek_blocks(
    seek_offset=5_000_000, seek_len=500_000,
    file_size=10_000_000,   block_size=1 << 20
)  # → [4] — only block 4 needed

Header / Footer / Prefix Construction

python
from qeltrix_v6._clib import (
    make_header, make_block_prefix, parse_block_prefix,
    header_size, v6c_size, index_entry_size, block_prefix_size
)

# Write a QLTX file header (returns bytes of size header_size())
hdr = make_header(
    block_size=1 << 20,
    total_blocks=10,
    original_file_size=10_000_000,
    footer_offset=10_500_000,
    footer_size=2048,
    flags=0,
    cipher_default=0,
    encrypted_mk=None,  # bytes or None (zeroed if None)
    mk_iv=None,
    mk_salt=None
)

# Write a 28-byte block prefix (magic + block_index + v6c_size + payload_size)
prefix = make_block_prefix(block_index=0, v6c_size=200, payload_size=50000)

# Parse a 28-byte block prefix back
# Returns (block_index: int, v6c_size: int, payload_size: int)
bidx, v6c_sz, payload_sz = parse_block_prefix(prefix_bytes)

# Size constants (from C struct definitions)
header_size()       # int — QLTX header bytes
v6c_size()          # int — V6-C metadata struct bytes
index_entry_size()  # int — one footer index entry bytes (128 bytes)
block_prefix_size() # int — block prefix bytes (28)

Permutation

python
from qeltrix_v6._clib import permute_block, unpermute_block

# Deterministic byte-level permutation keyed to seed (block_hash)
# Applied to compressed data before AES-GCM encryption
permuted   = permute_block(data, seed)    # seed = SHA-256 of original block
original   = unpermute_block(permuted, seed)
# Both implemented in C for speed. Returns bytes of same length as input.

Full C Function Reference

Python wrapperC functionDescription
block_count(file_size, block_size)qltx_block_countNumber of blocks needed
block_range(idx, file_sz, blk_sz)qltx_block_range(offset, size) of block N in original
seek_blocks(off, len, fsz, bsz)qltx_seek_blocksBlock indices covering a seek range
make_header(...)qltx_write_headerSerialize QLTX file header
make_block_prefix(idx, v6c_sz, pay_sz)qltx_write_block_prefix28-byte block framing prefix
parse_block_prefix(data)qltx_read_block_prefixParse 28-byte prefix back
permute_block(data, seed)qltx_permute_blockDeterministic permutation (C)
unpermute_block(data, seed)qltx_unpermute_blockReverse permutation (C)
header_size()qltx_header_sizeHeader struct size in bytes
v6c_size()qltx_v6c_sizeV6-C metadata struct size
index_entry_size()qltx_index_entry_sizeFooter index entry size
block_prefix_size()qltx_block_prefix_sizeBlock prefix size (28)
c_version()qltx_versionC library version string
via get_lib()qltx_listen_tcpCreate + bind TCP server socket
via get_lib()qltx_accept_tcpAccept connection, return fd
via get_lib()qltx_connect_tcpConnect to host:port, return fd
via get_lib()qltx_send_allSend all bytes on socket fd
via get_lib()qltx_recv_exactReceive exact N bytes (blocking)
via get_lib()qltx_recv_lineReceive one line (HTTP header parsing)
via get_lib()qltx_parse_range_headerParse HTTP Range: header → start/end int64
via get_lib()qltx_format_206_headerBuild HTTP 206 response header string
via get_lib()qltx_send_http_getSend HTTP GET request with optional Range
via get_lib()qltx_time_usMonotonic microseconds (uint64)
via get_lib()qltx_close_socketClose a socket fd
via get_lib()qltx_write_v6cSerialize V6-C metadata struct to bytes
via get_lib()qltx_read_v6cParse V6-C metadata bytes → fields (block_index, sizes, hash, iv, salt, cdk_blob, cipher_id)
via get_lib()qltx_write_index_entrySerialize one footer index entry (block_index, offsets, sizes, hash, iv, salt, cipher_id) to 128 bytes
via get_lib()qltx_read_headerParse raw QLTX header bytes into struct
via get_lib()qltx_footer_base_sizeSize of the QLTXFOOT base section (magic + count + hash = 48 bytes)
via get_lib()qltx_permute_table_genGenerate the permutation lookup table from a seed — called internally by qltx_permute_block

make_v6c() — V6-C Struct Builder

The only _clib function not exposed through __init__.py but used directly by container.py block workers. Builds the binary V6-C metadata struct that gets encrypted and stored with each block.

python
from qeltrix_v6._clib import make_v6c

# Build a V6-C metadata struct (encrypted per-block metadata)
# Returns bytes of size v6c_size()
v6c_bytes = make_v6c(
    block_index=0,
    original_size=1048576,    # plaintext block size
    compressed_size=800000,   # after zlib.compress()
    data_hash=block_hash,      # SHA-256 of original (32 bytes)
    iv=iv,                      # 12-byte nonce (stored in 16-byte field)
    salt=salt,                  # 32-byte HKDF salt
    encrypted_cdk=cdk_blob,   # 64-byte blob: [cdk_iv 12][wrapped_cdk 48][padding 4]
    cipher_id=0                 # 0=AES-256-GCM, 1=ChaCha20-Poly1305
)
# This bytes object is then passed to encrypt_v6c_metadata(v6c_bytes, mk)
# before being written to the container.

# Internal layout of the cdk_blob (64 bytes total):
# [0:12]  cdk_iv       — IV used to AES-GCM wrap the CDK
# [12:60] wrapped_cdk  — AES-GCM(CDK, MK, cdk_iv) = 32 + 16 tag bytes
# [60:64] padding      — zeroed

_bytes_ptr() — Low-Level Utility

python
from qeltrix_v6._clib import _bytes_ptr

# Cast a Python bytes object to a ctypes uint8* pointer
# Required to pass bytes to C functions that expect uint8_t*
ptr = _bytes_ptr(b"raw bytes")
# ctypes.POINTER(ctypes.c_uint8)

# Used internally in every _clib wrapper:
lib.qltx_permute_block(_bytes_ptr(data), len(data), out, _bytes_ptr(seed))
PYTHON API · CLI INTERNALS

CLI Internals

The helper functions inside qeltrix_v6.cli that glue the command-line interface to the Python API. Useful if you want to replicate the CLI's passphrase-to-MK derivation or progress bar in your own code.

_mk_from_passphrase()

Converts a human-readable passphrase string into the 32-byte Master Key used for all encryption operations. This is the exact function the CLI calls for every command.

python
from qeltrix_v6.cli import _mk_from_passphrase

# Derive a 32-byte master key from a passphrase
mk = _mk_from_passphrase("mysecretpassword")

# Equivalent to:
import hashlib
mk = hashlib.pbkdf2_hmac(
    "sha256",
    "mysecretpassword".encode(),
    b"QeltrixV6Salt",   # ← hardcoded PoC salt — see Security Notes
    200_000             # ← 200,000 PBKDF2 iterations
)
!

PoC limitation: The salt b"QeltrixV6Salt" is hardcoded. Two users with the same passphrase get the same MK, making precomputed rainbow tables possible. For production, generate a random 32-byte salt per container and store it in the header.

_progress()

The block-level progress callback used by pack(), unpack(), and seek() in the CLI. Prints an in-place progress bar to stdout.

python
from qeltrix_v6.cli import _progress

# Signature: _progress(done: int, total: int) -> None
# Prints:  [████████████████████] 100% (10/10 blocks)

# Use it as progress_cb= in pack() / unpack():
pack("file.txt", "file.qltx", mk, progress_cb=_progress)

# Or write your own:
def my_progress(done, total):
    print(f"Block {done}/{total} done")

pack("file.txt", "file.qltx", mk, progress_cb=my_progress)

CLI Command Functions

Each subcommand is a standalone function. They can be called programmatically by constructing an argparse.Namespace directly, which is useful for embedding CLI behaviour in scripts or tests without subprocess.

python
from qeltrix_v6.cli import cmd_pack, cmd_unpack, cmd_seek, cmd_info
import argparse

# Call cmd_pack programmatically
ns = argparse.Namespace(
    input="file.txt",
    output="file.qltx",
    passphrase="secret",
    cipher="aes",
    block_size=1024,   # KB
    workers=4
)
cmd_pack(ns)

# Call cmd_info programmatically
cmd_info(argparse.Namespace(input="file.qltx"))
FunctionArgs namespace fieldsDescription
cmd_pack(args)input, output, passphrase, cipher, block_size, workersRuns full pack with progress output
cmd_unpack(args)input, output, passphrase, no_verify, workersRuns full unpack with integrity report
cmd_seek(args)input, offset, length, passphrase, output, no_verify, workersSeek-extract; prints hex if no output file
cmd_gateway(args)host, port, passphrase, cipher, dest_host, dest_port, workersStarts gateway, blocks printing stats every 5s
cmd_serve(args)input, host, port, passphrase, workersStarts SeekServer, blocks until Ctrl+C
cmd_info(args)inputPrints container header info (no passphrase needed)

Entry Point

python
# __main__.py — invoked by `python -m qeltrix_v6`
from .cli import main
main()

# main() builds the argparse parser with all subcommands
# and dispatches to cmd_pack / cmd_unpack / etc.
# Can also be called directly:
from qeltrix_v6.cli import main
main()  # reads sys.argv
DEPLOYMENT · FILE SHARING

File Sharing & File Server

Qeltrix V6's SeekServer acts as an efficient encrypted file server. The encrypted .qltx file lives on disk; the server decrypts and serves content on demand over HTTP.

i

The SeekServer sends decrypted plaintext to clients over HTTP. The encryption protects the file at rest on disk. For in-transit protection, put the SeekServer behind an HTTPS reverse proxy (nginx, Caddy, Traefik).

Local File Sharing (Localhost)

1

Sender: Pack the file

powershell / bash
python -m qeltrix_v6 pack myfile.pdf myfile.qltx --passphrase shared_secret
2

Sender: Start the SeekServer

powershell / bash
python -m qeltrix_v6 serve myfile.qltx --port 7621 --passphrase shared_secret
3

Receiver: Download the file

The receiver gets the original decrypted file. No passphrase needed client-side — decryption happens on the server.

powershell
Invoke-WebRequest -Uri http://SERVER_IP:7621/ -OutFile received.pdf
curl
curl http://SERVER_IP:7621/ -o received.pdf

Share Encrypted Container Directly (Offline)

If you want the receiver to do their own decryption (not use the HTTP server), just share the .qltx file and passphrase separately:

workflow
Sender:    python -m qeltrix_v6 pack file.pdf file.qltx --passphrase secret
           [share file.qltx via email / USB / cloud storage]
           [share passphrase via Signal / in person]

Receiver:  python -m qeltrix_v6 unpack file.qltx recovered.pdf --passphrase secret

Secure Production Setup: HTTPS + SeekServer

Client ----HTTPS/TLS----> nginx (SSL termination) --HTTP--> SeekServer :7621 | reads .qltx from disk decrypts with MK sends plaintext Storage layer: .qltx file (encrypted at rest, unreadable without MK) Transit layer: HTTPS (encrypted in transit by nginx/TLS) MK: held in SeekServer memory only; never on disk; share via KMS/ECDH

Range Requests for Large Files

The SeekServer supports HTTP Range Requests natively. Any client that supports Range Requests (media players, browsers, download managers) can seek into the file without downloading it all:

powershell
# Get only the first 1 MB of a large encrypted file
Invoke-WebRequest -Uri http://localhost:7621/ `
    -Headers @{ Range = "bytes=0-1048575" } `
    -OutFile first_mb.bin

# Skip to byte 50,000,000 (e.g. seek in a video)
Invoke-WebRequest -Uri http://localhost:7621/ `
    -Headers @{ Range = "bytes=50000000-51048575" } `
    -OutFile video_chunk.bin
SECURITY NOTES

Security Notes

What is strong, what are the PoC limitations, and what must change before production use.

!

This is a proof of concept. The core encryption is sound. The key management layer has hardcoded values that are unsafe for production. Read below before deploying in any real context.

What Is Strong

Cryptographically sound

  • AES-256-GCM (AEAD)
  • ChaCha20-Poly1305 (AEAD)
  • HKDF-SHA256 for CDK derivation
  • Per-block independent CDKs
  • V6-C metadata encrypted with MK
  • MK never written to disk plaintext
  • AEAD auth tag detects any tampering
  • SHA-256 block integrity hashes

PoC weaknesses

  • Hardcoded PBKDF2 salt
  • No MK distribution mechanism
  • No key rotation
  • Symmetric MK only (no write/read role split)
  • SeekServer sends plaintext over HTTP
  • No authentication of clients

The Hardcoded PBKDF2 Salt

In cli.py, the passphrase-to-MK derivation uses a hardcoded salt b"QeltrixV6Salt". This means two people using the same passphrase get identical MKs, and an attacker could precompute a rainbow table of passphrase -> MK mappings valid for all V6 containers ever created.

python — current PoC (cli.py)
# CURRENT (PoC) — hardcoded salt, same passphrase = same MK always
return hashlib.pbkdf2_hmac("sha256", passphrase.encode(), b"QeltrixV6Salt", 200_000)

# PRODUCTION FIX — random salt per container, stored in header
salt = os.urandom(32)
return hashlib.pbkdf2_hmac("sha256", passphrase.encode(), salt, 200_000), salt

MK Distribution: Use ECDH or KMS

V6 does not handle MK distribution. For two nodes to share the same MK securely:

python — ECDH example
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes

# Node A generates keypair, sends public key to Node B
priv_a = X25519PrivateKey.generate()
pub_a  = priv_a.public_key()

# Node B generates keypair, sends public key to Node A
priv_b = X25519PrivateKey.generate()
pub_b  = priv_b.public_key()

# Both independently derive the same shared secret
shared_a = priv_a.exchange(pub_b)
shared_b = priv_b.exchange(pub_a)

# Derive identical MK on both sides — MK never on the wire
mk_a = HKDF(hashes.SHA256(), 32, None, b"QeltrixV6-MK").derive(shared_a)
mk_b = HKDF(hashes.SHA256(), 32, None, b"QeltrixV6-MK").derive(shared_b)
# mk_a == mk_b  (identical, without ever being transmitted)

SeekServer: Plaintext Over HTTP

The SeekServer decrypts server-side and sends plaintext. To secure in transit:

OptionHowEffort
nginx + Let's EncryptReverse proxy SeekServer behind nginx with free TLS certLow
CaddyCaddy auto-provisions TLS — one config lineVery low
Localhost onlyBind to 127.0.0.1 instead of 0.0.0.0, access via SSH tunnelMedium
VPNRun on private network, clients connect via VPNMedium

PoC to Production Checklist

ItemStatusFix
Block encryption (AES-256-GCM)Production-ready
CDK key derivation (HKDF)Production-ready
AEAD integrity verificationProduction-ready
PBKDF2 saltHardcodedRandom per-container salt stored in header
MK distributionNot implementedECDH handshake or KMS integration
Key rotationNot implementedRe-key API; schedule re-encryption
SeekServer TLSHTTP onlynginx / Caddy reverse proxy with HTTPS
Client authenticationNoneAdd bearer token / mTLS at reverse proxy