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.
{
"image": "<base64 PNG/JPEG>",
"payload": "<string, max 64 chars>"
}{
"watermarked_image": "<base64 PNG>"
}POST /decode
Detect and extract a watermark from an image.
{
"image": "<base64 PNG/JPEG>",
"expected_payload": "<string>"
}{
"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.
{
"video": "<base64 MP4/WebM, max 30s>",
"payload": "<string, max 64 chars>"
}{
"watermarked_video": "<base64 MP4>"
}POST /decode_video (optional)
Detect and extract a watermark from a video.
{
"video": "<base64 MP4/WebM>",
"expected_payload": "<string>"
}{
"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:
- 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.
- 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.
- 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.
- 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.
- 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.
- 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:
- Original Decoder: The cleaned image is sent to the original contestant's /decode endpoint with the expected payload.
- Independent Decoders: Two additional independent watermark detection systems also analyze the image.
- Verification: If ALL 3 decode checks return confidence < 0.1, the knockoff is verified and the submission is marked as knocked off.
- Disclosure: On verified knockoff, the contestant's GitHub repo URL is disclosed (derived from their keccak256 hash commitment).
API Reference
/api/contestGet contest info (name, timeline, prize pool, status, contestant count)
/api/contest/leaderboardGet ranked leaderboard with scores and status
/api/contest/submissionsList all submissions
/api/contest/submissionsRegister a new submission. Body: { contestantAddress, contestantAlias?, dockerImageUri, githubRepoHash, registrationTxHash? }
/api/contest/submissions/[submissionId]Get details for a specific submission including scores
/api/contest/submissions/[submissionId]/variationsGet watermarked variations. Query: ?limit=50&offset=0
/api/contest/knockoffsList knockoff attempts. Query: ?targetSubmissionId=xxx
/api/contest/knockoffsSubmit a knockoff attempt. Body: { targetSubmissionId, attackerAddress, variationIndex, cleanedImageUrl }
/api/llm.txtMachine-readable contest documentation (text/plain)
/api/agents.txtAgent 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: