Telegram Photo Wall is a real-time event photo wall powered by quantum-authenticated signatures. Users post photos and messages in Telegram groups; the system processes them through a quantum computing module (AWS Braket SV1) and renders them as draggable sticky notes on a web-based wall.
┌─────────────────────────────────────────────────────────────────────────────────┐
│ FRONTEND (User Interface) │
│ │
│ Browser ──→ CloudFront (CDN + TLS + Security Headers) │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────┐ │
│ │ Next.js Client Components │ │
│ │ ├── PhotoWall (polling + drag) │ │
│ │ ├── MessageCard (quantum badge) │ │
│ │ ├── Lightbox (image preview) │ │
│ │ └── Admin Dashboard (CRUD) │ │
│ └──────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────────┐
│ BACKEND / API LAYER │
│ │
│ ALB (secret header validation) ──→ ECS Fargate (Next.js Server) │
│ │
│ API Routes: │
│ ├── POST /api/webhook/[groupId] ← Telegram pushes messages here │
│ ├── GET /api/messages/[groupId] → Paginated message list + signed URLs │
│ ├── DELETE /api/messages/[groupId] → Admin: hide messages │
│ ├── PATCH /api/messages/[groupId] → Admin: save card positions │
│ ├── GET /api/groups → Available groups │
│ ├── POST /api/admin → Login (password verification) │
│ ├── GET /api/admin → Admin: group stats │
│ └── GET /api/health → Health check for ALB │
│ │
│ Security: │
│ ├── Telegram webhook secret token validation │
│ ├── Bearer token auth for admin/write endpoints │
│ ├── CloudFront → ALB secret header (blocks direct ALB access) │
│ └── Input sanitization (XSS prevention) │
└─────────────────────────────────────────────────────────────────────────────────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌──────────────────────────┐ ┌─────────────┐ ┌──────────────────────────────────┐
│ QUANTUM PROCESSING │ │ DATABASE │ │ STORAGE │
│ MODULE │ │ LAYER │ │ LAYER │
│ │ │ │ │ │
│ AWS Braket SV1 + DM1 │ │ DynamoDB │ │ S3 (photos) │
│ ├── 2-source QRNG │ │ ├── PK/SK │ │ ├── Private, encrypted │
│ ├── 2-qubit Bell state │ │ │ schema │ │ ├── Pre-signed URL access │
│ ├── ToyLWE signature │ │ ├── PAY_PER│ │ └── Versioned │
│ └── Local crypto │ │ │ _REQUEST│ │ │
│ fallback │ │ └── PITR │ │ S3 (Braket results) │
│ │ │ │ │ └── amazon-braket-* bucket │
│ ┌─────────────────────┐ │ │ │ │ │
│ │ ░░░░░░░░░░░░░░░░░░░ │ │ │ │ │ Secrets Manager │
│ │ ░ OpenClaw Deploy ░ │ │ │ │ │ ├── Bot tokens │
│ │ ░ (Future: hybrid ░ │ │ │ │ │ ├── Webhook secret │
│ │ ░ QPU routing) ░ │ │ │ │ │ └── Admin password │
│ │ ░░░░░░░░░░░░░░░░░░░ │ │ │ │ │ │
│ └─────────────────────┘ │ │ │ │ │
└──────────────────────────┘ └─────────────┘ └──────────────────────────────────┘
1. USER INPUT
User sends photo/text with @bot mention in Telegram group
│
▼
2. API VALIDATION
Telegram → POST /api/webhook/[groupId]
├── Validate X-Telegram-Bot-Api-Secret-Token header
├── Validate groupId format (regex: ^[a-zA-Z0-9_-]{1,64}$)
├── Verify group exists in configuration
├── Check message contains @bot mention
└── Sanitize text input (HTML entity encoding, max 4096 chars)
│
▼
3. PHOTO PROCESSING (if applicable)
├── Call Telegram getFile API → obtain file download URL
├── Download file (max 20MB limit)
├── Upload to S3: photos/{groupId}/{messageId}_{fileId}.{ext}
└── Supported: JPEG, PNG, GIF, WebP, BMP, TIFF, SVG, static stickers
│
▼
4. DATABASE WRITE (Phase 1)
Save message to DynamoDB with signatureStatus = "generating"
├── PK: GROUP#{groupId}
├── SK: MSG#{timestamp(15-padded)}#{messageId}
└── Fields: type, text, senderName, senderUsername, photoKey, timestamp
│
▼
5. QUANTUM TASK QUEUE
Check if sender already has a quantum signature in this group
│
├── [EXISTS] → Reuse existing signature (no Braket call)
│
└── [NEW SENDER] → Submit to AWS Braket (SV1 + DM1):
│
▼
6. DEVICE EXECUTION (AWS Braket Simulators)
Two-source QRNG (run concurrently):
├── Source x: 1-qubit Hadamard on SV1 (ideal), ~700 shots → per-shot bit stream
├── Source y: 1-qubit Hadamard on DM1 (noisy), ~700 shots → per-shot bit stream
└── Results written to: s3://amazon-braket-*/braket-results/{taskId}/results.json
Bell State Measurement:
├── Circuit: 2-qubit (H q[0] → CNOT q[0],q[1] → Measure)
├── Shots: 200
└── Output: Probability distribution [P(00), P(01), P(10), P(11)]
│
▼
7. RESULT AGGREGATION
├── Toeplitz two-source extractor: Ext(x, y) → 288 uniform bits
├── quantumNumber = readUInt32(out[0:4]) % 1001 (genuine [0, 1000])
├── r = out[4:36] (32 quantum-random bytes)
├── bellState = [P(00), P(01), P(10), P(11)]
├── ToyLWE Signature:
│ ├── 𝒮 = SHAKE-256(username + quantumNumber + r)
│ ├── Public key hash: SHA-256(𝒮[0:32]) → first 12 hex chars (uppercase)
│ └── Signature: SHA-256 chain → base64 (24 chars)
├── Visual color: HSL derived from quantum number + Bell state
└── Update DynamoDB: signatureStatus = "completed" + all signature fields (incl. quantumNonce)
│
▼
8. FRONTEND RENDERING
Browser polls GET /api/messages/[groupId] every 5 seconds
├── DynamoDB query (newest-first, filtered: hidden != true)
├── Generate S3 pre-signed URLs for photos (1-hour expiry)
└── Return JSON: messages[], nextCursor, groupName
│
▼
React components render:
├── PhotoWall: absolute-positioned cards, drag-to-reposition
├── MessageCard: sticky note with quantum badge (Q#number | pubKeyHash)
├── Leaderboard: top senders ranked by message count
└── Lightbox: click photo to zoom
Admin → POST /api/admin {password}
→ Verify against Secrets Manager (telegram/admin-password)
→ Return token (password re-used as bearer token)
→ Subsequent requests: Authorization: Bearer {token}
→ GET /api/admin (groups), DELETE /api/admin?action=hide|clear
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/webhook/[groupId] |
Telegram secret token | Receive Telegram updates |
| GET | /api/messages/[groupId] |
None (public) | Paginated messages with signed photo URLs |
| DELETE | /api/messages/[groupId]?sk=... |
Bearer token | Hide single message |
| DELETE | /api/messages/[groupId]?all=true |
Bearer token | Hide all messages in group |
| PATCH | /api/messages/[groupId] |
Bearer token | Save card position {sk, posX, posY} |
| GET | /api/groups |
None (public) | List available groups |
| POST | /api/admin |
None (password in body) | Admin login |
| GET | /api/admin |
Bearer token | Get groups + stats |
| DELETE | /api/admin?action=hide&groupId=X&sk=Y |
Bearer token | Hide message via admin |
| DELETE | /api/admin?action=clear&groupId=X |
Bearer token | Clear all group messages |
| GET | /api/health |
None | Health check (returns 200) |
Request (from Telegram):
{
"update_id": 123456789,
"message": {
"message_id": 42,
"from": { "id": 12345, "first_name": "Alice", "last_name": "Chen", "username": "alice_c" },
"chat": { "id": -1001234567890, "type": "supergroup" },
"date": 1700000000,
"text": "@my_bot Hello world!",
"photo": [
{ "file_id": "AgACAgI...", "width": 320, "height": 240 },
{ "file_id": "AgACAgI...", "width": 1280, "height": 960 }
],
"caption": "@my_bot Check this out",
"entities": [{ "type": "mention", "offset": 0, "length": 7 }]
}
}Response: {"status": "ok"} (always 200 to avoid Telegram retries)
Headers required: X-Telegram-Bot-Api-Secret-Token: <webhook_secret>
Query params:
limit(optional): 1-100, default 50cursor(optional): SK value for paginationafter(optional): timestamp for polling new messages
Response:
{
"messages": [
{
"messageId": 42,
"groupId": "demo-group",
"type": "photo",
"text": "Hello world!",
"photoUrl": "https://s3.amazonaws.com/...?X-Amz-Signature=...",
"senderName": "Alice Chen",
"senderUsername": "alice_c",
"timestamp": 1700000000000,
"createdAt": "2024-11-14T22:13:20.000Z",
"sk": "MSG#000001700000000#42",
"signatureStatus": "completed",
"quantumNumber": 452,
"publicKeyHash": "7B284BB3D413",
"quantumSignature": "a3F2b8c9d0e1f2g3h4i5",
"bellState": [0.49, 0.01, 0.01, 0.49],
"visualColor": "hsl(207, 85%, 55%)",
"posX": 0.35,
"posY": 0.22
}
],
"nextCursor": "MSG#000001699999000#41",
"groupName": "Demo Photo Wall",
"groupId": "demo-group"
}Request:
{
"sk": "MSG#000001700000000#42",
"posX": 0.45,
"posY": 0.30
}Response: {"ok": true}
Request: {"password": "admin-secret-value"}
Response (success): {"ok": true, "token": "admin-secret-value"}
Response (failure): {"error": "Invalid password"} (401)
The system uses polling rather than WebSocket connections:
- Interval: 5 seconds
- Mechanism: Client-side
setIntervalcallingGET /api/messages/[groupId]?after={lastTimestamp} - Rationale: Simpler architecture for ECS Fargate (no sticky sessions needed), sufficient for event use case where message frequency is low-to-moderate
Future enhancement: WebSocket via API Gateway for sub-second updates.
The system is designed for future OpenClaw deployment with the following integration points:
# openclaw.yml (future)
service:
name: telegram-photo-wall
runtime: container
image: ./photo-wall
port: 3000
health_check: /api/health
scaling:
min_instances: 1
max_instances: 4
target_cpu: 70
environment:
TABLE_NAME: ${resource.dynamodb.table_name}
BUCKET_NAME: ${resource.s3.bucket_name}
WEBHOOK_SECRET_ARN: ${secret.webhook_secret.arn}
ADMIN_SECRET_ARN: ${secret.admin_password.arn}
GROUP_CONFIG: ${var.telegram_groups_json}
AWS_REGION_NAME: ${region}
BRAKET_BUCKET: ${resource.braket_bucket.name}
quantum:
provider: aws-braket
device: arn:aws:braket:::device/quantum-simulator/amazon/sv1
fallback: local-crypto
result_bucket: ${resource.braket_bucket.name}| Variable | Source | Description |
|---|---|---|
TABLE_NAME |
CDK/OpenClaw resource | DynamoDB table name |
BUCKET_NAME |
CDK/OpenClaw resource | S3 photo bucket name |
BRAKET_BUCKET |
CDK/OpenClaw resource | S3 bucket for Braket results |
WEBHOOK_SECRET_ARN |
Secrets Manager | ARN of webhook verification secret |
ADMIN_SECRET_ARN |
Secrets Manager | ARN of admin password secret |
GROUP_CONFIG |
JSON string | Array of group configurations |
AWS_REGION_NAME |
Deployment config | AWS region for SDK clients |
NODE_ENV |
Fixed: "production" | Node.js environment |
PORT |
Fixed: "3000" | Server listen port |
| Parameter | Value | Rationale |
|---|---|---|
| Min instances | 1 | Always-on for webhook reception |
| Max instances | 4 | Handle event spikes (100+ concurrent users) |
| CPU target | 70% | Scale out before saturation |
| Scale-out cooldown | 30s | Respond quickly to traffic bursts |
| Scale-in cooldown | 60s | Avoid flapping |
| Memory | 1024 MB | Next.js + image processing headroom |
| CPU | 512 (0.5 vCPU) | Sufficient for I/O-bound workload |
| Parameter | Type | Required | Description |
|---|---|---|---|
username |
string | Yes | Telegram sender display name (signed alongside the quantum nonce) |
messageText |
string | Yes | Message content or fallback msg-{id} |
| Source circuits | enum | Internal | 1-qubit Hadamard ×2 (SV1 ideal + DM1 noisy) and bell (2-qubit entanglement) |
| Shots | number | Internal | ~700 per QRNG source, 200 (Bell) |
| Backend | string | Config | …/amazon/sv1 + …/amazon/dm1 |
| Field | Type | Description |
|---|---|---|
quantumNumber |
number (0-1000) | Quantum random number from the two-source extractor |
publicKeyHash |
string (12 hex chars) | ToyLWE public key hash (e.g., "7B284BB3D413") |
signature |
string (24 chars, base64) | ToyLWE signature |
nonce |
string (64 hex chars) | The 32 quantum-random bytes r (persisted as quantumNonce, audit-only) |
bellState |
[number, number, number, number] | Bell state probabilities [P(00), P(01), P(10), P(11)] |
algorithm |
string | "ToyLWE-2Source-Toeplitz" or "ToyLWE-local-fallback" |
visualColor |
string | HSL color derived from quantum data (e.g., "hsl(207, 85%, 55%)") |
device |
string | "SV1+DM1" or "local-fallback" |
- Braket timeout (30s) → falls back to local crypto
- Braket task FAILED/CANCELLED → falls back to local crypto
- S3 results unreadable → falls back to local crypto
- All fallbacks produce deterministic-looking but cryptographically-seeded results
- Array of message objects (see GET /api/messages schema above)
- Group metadata (name, groupId)
- Positioned sticky-note cards (absolute % positioning, draggable)
- Quantum badge on each card:
Q#{number} | {publicKeyHash} - HSL-colored card borders from
visualColor - Photo thumbnails with click-to-expand lightbox
- Leaderboard showing top message senders
- Event logo overlay (from
/public/logo.png)