TectraTectra
How It Works

Under the hood

Every signed image passes through a multi-layer pipeline that combines cryptographic signatures, invisible watermarking, content hashing, and blockchain anchoring. This page explains each layer in detail.

Signing Pipeline

When you call POST /api/v1/sign, the backend runs these steps in sequence:

Upload image
  │
  ├─ Validate (PNG/JPEG/WebP/MP4/WebM, max 50MB image / 500MB video)
  ├─ Fetch Ed25519 signing key from DB (verify ownership)
  │
  ├─ compute_sha256(bytes)          → exact fingerprint
  ├─ compute_perceptual_hash(bytes) → fuzzy fingerprint
  │
  ├─ fingerprint_data = sha256|phash|timestamp|user_id
  ├─ decrypt_private_key(encrypted_key)  ← Fernet decryption
  ├─ signature = Ed25519.sign(private_key, fingerprint_data)
  │
  ├─ watermark_payload = SHA256(signature)[:16 bytes]
  ├─ embed_watermark(image, payload)     ← DWT+DCT steganography
  │
  ├─ [optional] add_c2pa_manifest(image, org, sha256, pubkey)
  │
  ├─ Save ContentRecord to PostgreSQL
  ├─ Push to Redis batch queue
  ├─ Dispatch webhook (if configured) → POST to subscriber URL
  │
  └─ Return { record_id, sha256, certificate, watermarked_file }

Invisible Watermarking (DWT+DCT)

The watermark is a 128-bit (16-byte) payload derived from the Ed25519 signature. It is embedded using a two-stage transform:

Stage 1 - Discrete Wavelet Transform (DWT)

The image is decomposed into frequency sub-bands. Low-frequency components carry most visual information; the watermark is embedded in mid-frequency coefficients that survive compression.

Stage 2 - Discrete Cosine Transform (DCT)

Within the DWT sub-bands, DCT is applied per block. The payload bits modulate specific DCT coefficients. This dual-domain embedding makes the watermark robust against JPEG compression, cropping, and re-encoding.

Robustness

The watermark survives JPEG compression down to ~60% quality, social media re-encoding (Instagram, Twitter), screenshots, and moderate cropping. Verification uses Hamming distance ≤ 6 bits for fuzzy matching.

Dual Hashing

SHA-256 (Exact Match)

A SHA-256 hash of the raw image bytes is computed and stored. This provides exact-match verification - any single bit change produces a completely different hash. Both the original file hash and the watermarked file hash are stored, so the original can still be verified after watermarking.

Perceptual Hash - pHash (Fuzzy Match)

A perceptual hash (using the imagehash library) is also computed. pHash encodes the visual structure of an image into a 64-bit integer. Two images with Hamming distance ≤ threshold are considered the same image. This catches re-compressed, cropped, or slightly edited versions.

Verification pre-filters candidates using a 4-character hash prefix before full Hamming comparison, making lookup fast even at scale.

Video Signing (Segment-Chained Hashing)

Videos use a specialized segment-chained hashing architecture that divides the timeline into segments, computes a chained hash across segments, and stores one record per segment rather than per frame. This is both faster to process and more resilient to partial corruption.

How It Works

Upload video (MP4/WebM, up to 5GB depending on plan)
  │
  ├─ Stream to MinIO/S3, compute SHA-256 on the fly
  ├─ Create parent ContentRecord (status: "processing")
  ├─ Dispatch background Celery task
  │
  └─ Background task:
       │
       ├─ Extract keyframes (seek-based, adaptive FPS)
       │    └─ ≤5min: 1fps │ ≤30min: 0.5fps │ ≤2hr: 0.2fps │ >2hr: 0.1fps
       │
       ├─ Parallel pHash computation (ProcessPoolExecutor)
       │    └─ Each frame → 64-bit perceptual hash
       │
       ├─ Group frames into 5-second segments
       │
       ├─ Build segment chain:
       │    segment_hash[0] = SHA256(ph0 || ph1 || ... || 0x00)
       │    segment_hash[1] = SHA256(ph3 || ph4 || ... || segment_hash[0])
       │    segment_hash[N] = SHA256(phK || ...     || segment_hash[N-1])
       │
       ├─ Store one ContentRecord per segment (not per frame)
       │    └─ segment_hash, aggregate_phash, chain JSON
       │
       ├─ Store final_chain_hash on parent record
       └─ Push to Merkle batch queue → Polygon blockchain

Segment Chain Design

Each segment's hash is computed from the perceptual hashes of its constituent frames concatenated with the previous segment's hash. This creates a forward chain where each segment depends on all prior segments:

Chaining formula

segment_hash[i] = SHA256(frame_phash_0 || frame_phash_1 || ... || segment_hash[i-1])

The first segment chains from a zero seed (0x00 * 32). Each subsequent segment incorporates the hash of the previous segment, forming an ordered chain.

Tamper Detection + Corruption Tolerance

If a single segment is tampered with, the chain hash breaks for that segment and all subsequent ones - making tampering detectable. But each segment is also individually verifiable via its aggregate pHash, so a few corrupted segments (common with video re-encoding) don't invalidate the entire video.

Why Segments, Not Frames

Per-frame (old)

5-min video = ~300 DB rows, 300+ Merkle entries, 10 separate DB queries per verification

Per-segment (new)

5-min video = ~60 DB rows, ~60 Merkle entries, 1 bulk DB query per verification

Video Verification

When verifying a video, the system re-extracts keyframes, groups them into the same segments, and recomputes the segment chain. It then compares against stored segments using:

1

Exact video SHA-256 match (video completely untouched)

2

Exact segment hash match for each segment (segment pixels unchanged)

3

Segment aggregate pHash similarity (segment re-encoded but visually identical)

4

Blockchain check via parent record's Merkle proof (single check)

The confidence score is verified_segments / total_segments. A video is considered authentic if ≥50% of segments verify, or if the overall check confidence is ≥50%. The response includes a chain_intact flag indicating whether all segments matched exactly (no re-encoding or tampering).

Ed25519 Cryptographic Signing

Each organization registers Ed25519 keypairs (via PyNaCl). The private key is encrypted at rest using Fernet symmetric encryption - only the organization's Fernet key can decrypt it. This means even a database breach cannot expose private signing keys.

# What is signed:
fingerprint = f"{sha256}|{phash}|{timestamp}|{user_id}"
signature   = nacl.signing.SigningKey(private_key).sign(fingerprint)

# Stored:
signature_hex = signature.hex()
public_key_hex = signing_key.verify_key.encode().hex()

The signature is verifiable by anyone with the signer's public key - no Tectra servers required.

Blockchain Anchoring (Polygon + Merkle Trees)

Batch Accumulation

Content records are not anchored individually (which would be expensive). Instead, records are batched:

Every ~10 seconds, Celery task runs:
  1. Collect all pending hashes from Redis queue
  2. Build Merkle tree from {record_id: sha256} leaves
  3. Compute merkle_root = tree.root
  4. Call ProvenanceRegistry.registerBatch(
       merkleRoot, leafCount, signerPubKey, timestamp
     ) on Polygon
  5. Store tx_hash, merkle_root, and per-record proofs in PostgreSQL

Merkle Proof Verification

Each content record stores its Merkle proof path. During verification, the proof is re-verified client-side against the on-chain Merkle root. This means verification is fully trustless - you only need the Polygon contract.

Smart Contract

The ProvenanceRegistry.sol contract is deployed on Polygon. It stores a mapping from Merkle root to batch metadata (timestamp, signer pub key, leaf count). See the Blockchain docs for the full ABI.

C2PA Content Credentials

Tectra is a C2PA member. Optionally, Tectra embeds a C2PA Content Credentials manifest directly into the image file. This is the industry standard for content provenance, supported natively by Adobe Photoshop, Lightroom, and major media platforms.

The manifest includes: a c2pa.actions claim (content created), and a custom tectra.provenance assertion containing the SHA-256, pHash, signer public key, and organization name.

C2PA requires a valid CA-issued ES256 certificate. Self-signed certs will embed but may show a warning in validators. See setup instructions.

Verification Pipeline

When POST /api/v1/verify is called, four independent checks run:

1

Watermark Check

Extract the DWT+DCT watermark payload. Try exact match, then fuzzy Hamming ≤ 6 bits against all stored payloads.

2

SHA-256 Check

Hash the uploaded file. Check against both original and watermarked hashes in the database.

3

Perceptual Hash Check

Compute pHash. Pre-filter by hash prefix, then Hamming distance comparison. Catches re-compressed copies.

4

Blockchain Check

Look up content hash on-chain. If found in a batch, re-verify the Merkle proof against the anchored root.

The confidence score is passed_checks / total_checks. A score ≥ 40% returns authentic: true. See the Verification Guide for details.

Verification clients

You can verify images through the REST API, the Python or TypeScript SDKs, the tectra-verify open-source package (no account needed), or the Tectra browser extension for one-click verification on any webpage.

Next: Use Cases

See how different industries integrate Tectra.

Use Cases