Candidate: Prudhvi Ram Maddina
Email: maddinaprudhviram@gmail.com
# Solve all 4 layers, submit, and write results.txt — all in one run
node main.js
# Pass key explicitly (overrides environment.js)
node main.js <API_KEY>
# Dry-run: compute every answer without submitting or writing results.txt
node main.js --dry-run
# Write results to a custom path
node main.js --out /tmp/my-results.txt
# Run unit tests (no network, no API key needed)
npm testRequirements: Node.js ≥ 14, no external packages (npm install is not needed).
API key: stored in environment.js (git-ignored) — no need to pass it on every run.
| Layer | Submit type | Answer | Status |
|---|---|---|---|
| 1 | content_hash |
2cc4fd2f6882128d1946e7ba84cadd4bb48b118cd86b0f4748512333c956bba3 |
✓ |
| 2 | decrypted_hash |
1d829d859de7ccbf86d6ee985ee0993c144d77e305ea66f387cc93ea7d201387 |
✓ |
| 3 | algorithm_answer |
2d73ee9e3256d75aa40bb7c3998e0afd5b4479ce68fe6371558f891078c1e5d6 |
✓ |
| 4 | analysis |
(dataset statistics — see Layer 4 below) | ✓ |
Full HTTP request/response logs for every submission are in results.txt.
Task: Prove byte-level integrity of the 500-record encrypted dataset.
Approach:
GET /api/v1/dataset?page=N&page_size=100— readtotalfrom the first response to drive pagination dynamically (currently 5 pages of 100).- Each record is a base64 string that decodes to exactly 256 raw bytes (RSA-2048 ciphertext).
- Concatenate all 500 × 256 = 128,000 decoded bytes in page order.
SHA-256(Buffer.concat(buffers)).
Key insight: hash the decoded binary, not the base64 strings or the surrounding JSON.
Task: Decrypt the 500 RSA-2048/PKCS#1v1.5 ciphertexts and prove correctness.
Approach:
GET /api/v1/private-key→ PKCS#8 PEM (2048-bit RSA, 1 704 chars).crypto.createPrivateKey(pem)thencrypto.privateDecrypt({ padding: RSA_PKCS1_PADDING }, buf).- Each plaintext is a JSON object with 7 fields:
endpoint,latency_ms,method,request_bytes,status_code,timestamp,user_segment. - Concatenate all 500 plaintext strings in original encrypted-array order, then SHA-256.
Key insight: reuse the same encrypted records already fetched for Layer 1 — no extra API calls.
Task: Solve 10,000 queries against a 50,000-record jumbo dataset in O(N + K) time.
See: challenges/algorithm/ for the standalone solver, benchmarks, and unit tests.
Approach:
| Step | API call | Rate-limit cost |
|---|---|---|
| Fetch 10 000 queries | GET /api/v1/challenges/algorithm/queries |
0 |
| Discover bulk endpoint | OPTIONS /api/v1/dataset/jumbo |
0 |
| Mint download token | POST /api/v1/dataset/jumbo/bulk-request |
1 |
| Download 50 000 records | GET /api/v1/dataset/jumbo/bulk/{token} (no auth) |
0 |
O(N) preprocessing builds three structures, then every query is O(1) or O(log N):
| Query op | Structure | Cost |
|---|---|---|
count |
Map<"segment:status_code", number> |
O(1) |
exists |
Set<"endpoint:method:status:segment"> |
O(1) |
range_count |
Two sorted number[] + binary search |
O(log N) |
SHA-256(",".join(decimal answers)) → submitted as algorithm_answer.
Measured: preprocessing 70–108 ms, 10 000 queries 7–11 ms, full run ~43 s (network-bound).
Data source: GET /api/v1/challenges/ui/dataset (500 decrypted records, no auth).
The API uses RSA-2048/PKCS#1v1.5 for per-record encryption. PKCS#1v1.5 is functionally correct but susceptible to Bleichenbacher padding-oracle attacks; PKCS#1 OAEP (v2.2) is the safer choice for new designs. Serving the private key directly from an authenticated endpoint is non-standard — production systems use a KMS and never export the raw private key.
The rate-limiting design (5 req/min, 1 token/call) deliberately pushes candidates toward the
batch and bulk endpoints. Discovering those via OPTIONS responses and Link headers mirrors
real API exploration patterns.
Tradeoffs observed:
| Observation | Better alternative |
|---|---|
| Per-record RSA encryption | Hybrid: AES-256-GCM per-record, RSA-wrapped AES key |
| PKCS#1v1.5 padding | PKCS#1 OAEP (RSA_PKCS1_OAEP_PADDING) |
| Private key served via API | Server-side decrypt endpoint; key never leaves KMS |
| Pagination hard-capped at 100 | Bulk/streaming endpoints (correctly provided here) |
├── main.js ← entry point — solves all 4 layers and writes results.txt
├── environment.js ← API key (git-ignored)
├── package.json ← npm scripts (start, test)
├── results.txt ← full HTTP request/response log for each layer
├── RATIONALE.md ← full solution rationale, trials, bugs, and design decisions
├── README.md
├── .gitignore
└── challenges/
└── algorithm/
├── solver.js ← standalone Layer 3 solver
├── README.md ← how to run the standalone solver
├── RATIONALE.md ← O(N+K) design rationale and benchmarks
└── tests/
└── solver.test.js ← 17 unit tests (no network, no API key)