Skip to content

iampram/layered-puzzle

Repository files navigation

SE Assessment — Solution

Candidate: Prudhvi Ram Maddina
Email: maddinaprudhviram@gmail.com


Quick Start

# 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 test

Requirements: 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.


Results

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.


Layer 1 — Content Hash

Task: Prove byte-level integrity of the 500-record encrypted dataset.

Approach:

  1. GET /api/v1/dataset?page=N&page_size=100 — read total from the first response to drive pagination dynamically (currently 5 pages of 100).
  2. Each record is a base64 string that decodes to exactly 256 raw bytes (RSA-2048 ciphertext).
  3. Concatenate all 500 × 256 = 128,000 decoded bytes in page order.
  4. SHA-256(Buffer.concat(buffers)).

Key insight: hash the decoded binary, not the base64 strings or the surrounding JSON.


Layer 2 — Decrypted Hash

Task: Decrypt the 500 RSA-2048/PKCS#1v1.5 ciphertexts and prove correctness.

Approach:

  1. GET /api/v1/private-key → PKCS#8 PEM (2048-bit RSA, 1 704 chars).
  2. crypto.createPrivateKey(pem) then crypto.privateDecrypt({ padding: RSA_PKCS1_PADDING }, buf).
  3. Each plaintext is a JSON object with 7 fields: endpoint, latency_ms, method, request_bytes, status_code, timestamp, user_segment.
  4. 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.


Layer 3 — Algorithm Answer

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).


Layer 4 — Analysis

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)

Repository Layout

├── 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)

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors