Documentation

Technical reference for building, submitting, and attacking watermarking systems.

Docker Image Specification

Your Docker image must run an HTTP server on port 8080 and implement the following three endpoints:

GET /health

Returns 200 when the system is ready to process requests.

Response: { "status": "ok" }

POST /encode

Embed an invisible watermark into an image.

Request
{
  "image": "<base64 PNG/JPEG>",
  "payload": "<string, max 64 chars>"
}
Response
{
  "watermarked_image": "<base64 PNG>"
}

POST /decode

Detect and extract a watermark from an image.

Request
{
  "image": "<base64 PNG/JPEG>",
  "expected_payload": "<string>"
}
Response
{
  "detected": true,
  "confidence": 0.95,
  "payload": "<extracted string>"
}

POST /encode_video (optional)

Embed an invisible watermark into a video. Only required if your system supports video.

Request
{
  "video": "<base64 MP4/WebM, max 30s>",
  "payload": "<string, max 64 chars>"
}
Response
{
  "watermarked_video": "<base64 MP4>"
}

POST /decode_video (optional)

Detect and extract a watermark from a video.

Request
{
  "video": "<base64 MP4/WebM>",
  "expected_payload": "<string>"
}
Response
{
  "detected": true,
  "confidence": 0.95,
  "payload": "<extracted string>"
}

Constraints

  • Startup: must pass /health within 30 seconds
  • Request timeout: 10 seconds per image encode/decode, 60 seconds per video
  • Memory limit: 8 GB RAM
  • CPU only (no GPU)
  • No outbound network access
  • Payload: UTF-8 string, max 64 characters

How Evaluation Works

After submission, your Docker image is pulled and evaluated in an isolated sandbox environment:

  1. Image Pull & Start: Your container is started with 8 GB RAM, no GPU, no network. We wait up to 30s for /health to return 200.
  2. Encoding (1,000 images + 100 videos): Each test image is sent to /encode and each test video to /encode_video (if supported) with a unique random payload (8-32 chars). The watermarked media are saved.
  3. Accuracy Check: Each watermarked item is sent to /decode (or /decode_video). The accuracy score = count of exact payload matches / total items. Additionally, a set of unwatermarked media is tested—any false positive detection is disqualifying.
  4. Robustness Check: Each watermarked image is subjected to a suite of transformations (JPEG 50-90, crop 10-30%, resize 50-200%, rotation, social media simulation). The transformed image is decoded. Robustness = percentage of transforms that still decode correctly.
  5. Appearance Check: SSIM and PSNR between original and watermarked images are computed. For the top 10 by total score, a manual review panel scores appearance 1-10.
  6. Total Score: (Accuracy x 0.4) + (Robustness x 0.4) + (Appearance x 0.2)

Knockoff Verification

When someone submits a knockoff (cleaned image), the verification process is:

  1. Original Decoder: The cleaned image is sent to the original contestant's /decode endpoint with the expected payload.
  2. Independent Decoders: Two additional independent watermark detection systems also analyze the image.
  3. Verification: If ALL 3 decode checks return confidence < 0.1, the knockoff is verified and the submission is marked as knocked off.
  4. Disclosure: On verified knockoff, the contestant's GitHub repo URL is disclosed (derived from their keccak256 hash commitment).

API Reference

GET
/api/contest

Get contest info (name, timeline, prize pool, status, contestant count)

GET
/api/contest/leaderboard

Get ranked leaderboard with scores and status

GET
/api/contest/submissions

List all submissions

POST
/api/contest/submissions

Register a new submission. Body: { contestantAddress, contestantAlias?, dockerImageUri, githubRepoHash, registrationTxHash? }

GET
/api/contest/submissions/[submissionId]

Get details for a specific submission including scores

GET
/api/contest/submissions/[submissionId]/variations

Get watermarked variations. Query: ?limit=50&offset=0

GET
/api/contest/knockoffs

List knockoff attempts. Query: ?targetSubmissionId=xxx

POST
/api/contest/knockoffs

Submit a knockoff attempt. Body: { targetSubmissionId, attackerAddress, variationIndex, cleanedImageUrl }

GET
/api/llm.txt

Machine-readable contest documentation (text/plain)

GET
/api/agents.txt

Agent discovery document (text/plain)

Example curl Commands

Get contest info

curl https://contest.privatebond.tech/api/contest

View leaderboard

curl https://contest.privatebond.tech/api/contest/leaderboard

Register a submission

curl -X POST https://contest.privatebond.tech/api/contest/submissions \
  -H "Content-Type: application/json" \
  -d '{
    "contestantAddress": "0xYourWalletAddress",
    "contestantAlias": "MyTeam",
    "dockerImageUri": "gcr.io/my-project/watermark:v1",
    "githubRepoHash": "0xabc123..."
  }'

Get variations for a submission

curl "https://contest.privatebond.tech/api/contest/submissions/sub_abc123/variations?limit=10&offset=0"

Submit a knockoff attack

curl -X POST https://contest.privatebond.tech/api/contest/knockoffs \
  -H "Content-Type: application/json" \
  -d '{
    "targetSubmissionId": "sub_abc123",
    "attackerAddress": "0xAttackerWallet",
    "variationIndex": 42,
    "cleanedImageUrl": "https://storage.example.com/cleaned.png"
  }'

Tips for Building a Good System

Spread the payload across the image

Embedding redundantly across spatial and frequency domains makes the watermark harder to remove with localized attacks like cropping or inpainting.

Optimize for JPEG survival

JPEG compression is the most common real-world transform. Consider embedding in DCT coefficients or using a learned approach trained with JPEG differentiable approximation.

Use error-correcting codes

Encode your payload with BCH, Reed-Solomon, or LDPC codes before embedding. This adds redundancy that allows recovery even when some bits are corrupted by image transforms.

Balance visibility and robustness

Stronger embedding means better robustness but worse visual quality. The scoring weights both equally (40% each), so find the sweet spot. Aim for PSNR > 38 dB and SSIM > 0.95.

Test against social media pipelines

Instagram, Twitter, and Facebook each apply different recompression and resizing. Test your system by uploading watermarked images and downloading them to see if the watermark survives.

Keep it fast

You have 10 seconds per image, 60 seconds per video. Consider using optimized C/Rust libraries or lightweight neural networks. Profile your encode/decode pipeline and eliminate bottlenecks.

Video: don't just watermark frames

Frame-by-frame image watermarking fails on video because temporal compression (H.264/H.265) destroys per-frame spatial modifications differently across I/P/B frames. Consider temporal-domain embedding, motion-vector manipulation, or techniques that exploit inter-frame redundancy rather than fighting it.

Machine-readable documentation: