Files
nx9-chronoseal-rs/docs/API.md
T
thakares 4b27a342d1 docs: comprehensive ARCHITECTURE, DEPLOYMENT, API, and THREAT_MODEL
ARCHITECTURE.md
- Full component map with ASCII diagram
- Complete session lifecycle (init + heartbeat + failure path)
- Cryptographic protocol spec (hash chain formula, canonical JSON)
- Stack machine instruction set table with stack effects
- Behavioral validation thresholds
- SQLite schema, threat model summary, module reference

DEPLOYMENT.md
- Build instructions (WASM + server + convenience script)
- native binary, systemd (with hardened sandbox notes), Docker
- nginx, Nginx Proxy Manager, and HAProxy reverse proxy configs
- Integration options (sidecar vs proxy-only)
- Full configuration table with all constants
- Observability (RUST_LOG levels), health check, security checklist

API.md
- Full /init and /hb request/response schemas with field tables
- Canonical signing payload specification
- Complete validation rules table (all 13 rejection conditions)
- Hash chain byte-level specification
- WASM exported function reference

THREAT_MODEL.md
- Four attacker profiles (script kiddie → sophisticated adversary)
- Eight attack vectors with mitigations (replay, forgery, hijack, DoS…)
- Explicit out-of-scope limitations
- Operational security notes (CORS, TLS, log level, SQLite)
2026-05-09 18:04:13 +05:30

6.9 KiB
Raw Blame History

ChronoSeal — API Reference

Base URL

All endpoints are relative to the server root. In development: http://localhost:3000. In production: your HTTPS domain via reverse proxy.


Endpoints

POST /init

Initialise a new session. Called once per page load, immediately after the WASM module generates an Ed25519 keypair.

Request

POST /init
Content-Type: application/json
{
  "public_key": "hex-encoded 32-byte Ed25519 verifying key"
}
Field Type Description
public_key string Hex-encoded 32-byte Ed25519 verifying key generated by the WASM module

Response 200 OK

{
  "session_id":    "64-char hex string (32 bytes)",
  "salt":          "32-char hex string (16 bytes)",
  "opcodes_b64":   "base64-encoded VM program (8–16 opcodes)",
  "initial_hash":  "64-char hex string (32 bytes Blake3)",
  "expires_at":    1234567890123
}
Field Type Description
session_id string Opaque session identifier; include in every heartbeat
salt string Initial salt; used to compute H(0) and first H(1)
opcodes_b64 string Base64 VM program; execute with run_program() on every heartbeat
initial_hash string H(0) = Blake3(session_id ║ pub_key ║ salt); the first prev_hash
expires_at number Unix timestamp in milliseconds; session expires after 30 minutes of inactivity

Error

Returns 500 Internal Server Error only on server-side failures (DB errors, invalid public key length). No meaningful error body is returned.


POST /hb

Submit a heartbeat. Called every 12–25 seconds with uniform random jitter.

Request

POST /hb
Content-Type: application/json
{
  "session_id":   "64-char hex",
  "prev_hash":    "64-char hex",
  "timestamp":    1234567890123,
  "entropy_data": {
    "events": [
      { "x": 412.0, "y": 308.5, "t": 1234.567 },
      { "x": 415.2, "y": 310.1, "t": 1285.123 }
    ]
  },
  "stack_state": {
    "stack": [2971406957, 1234567890],
    "ip": 42
  },
  "fingerprint": {
    "aspectRatio":        "1.7777777778",
    "devicePixelRatio":   "2",
    "hardwareConcurrency": 8
  },
  "signature": "128-char hex Ed25519 signature"
}
Field Type Description
session_id string Session ID from /init
prev_hash string Hash chain head from previous heartbeat (or initial_hash for the first)
timestamp number Date.now() in milliseconds; must be within ±30s of server time
entropy_data.events array Mouse events since previous heartbeat; each has x, y (px), t (performance.now ms)
stack_state.stack array u32[] result of executing the VM program
stack_state.ip number Instruction pointer after execution
fingerprint.aspectRatio string (screen.width / screen.height).toFixed(10)
fingerprint.devicePixelRatio string String(window.devicePixelRatio)
fingerprint.hardwareConcurrency number navigator.hardwareConcurrency || 1
signature string Hex-encoded 64-byte Ed25519 signature over the canonical payload

Canonical Signing Payload

The client signs the following JSON object. Top-level keys must be sorted alphabetically. Nested object keys follow their natural serialisation order.

{
  "entropyData":  { "events": [{ "t": …, "x": …, "y": … }] },
  "fingerprint":  { "aspectRatio": "…", "devicePixelRatio": "…", "hardwareConcurrency": … },
  "prevHash":     "…",
  "sessionId":    "…",
  "stackState":   { "ip": …, "stack": […] },
  "timestamp":    …
}

Note: field names in the signing payload use camelCase (sessionId, prevHash, entropyData, stackState) while the request body uses snake_case (session_id, prev_hash, entropy_data, stack_state).

Response 200 OK — Accepted

{
  "status":    "ok",
  "next_salt": "32-char hex string (16 bytes)"
}

The client must:

  1. Capture sentSalt = currentSalt before updating.
  2. Set currentSalt = next_salt.
  3. Compute prevHash = compute_next_hash(prevHash, timestamp, entropyJson, stackStateJson, sentSalt).

Response 200 OK — Rejected

{
  "status": "ok"
}

next_salt is absent. The response body is intentionally identical in structure. Rejections are silent — the caller cannot distinguish a validation failure from a rate limit hit or an expired session.

The client should log a warning and continue scheduling heartbeats (they will continue to fail until the page is reloaded and a new session is established).


Validation Rules (Server-Side)

Heartbeats are rejected (silently) if any of the following checks fail:

Check Condition for rejection
Rate limit > 5 requests per 10-second window for this session_id
Session not found session_id not in SQLite
Session expired current_time_ms > expires_at
Signature invalid Ed25519 verification fails against stored public key
Hash chain broken hex(prev_hash) ≠ stored last_hash
Timestamp drift |server_now_ms - timestamp| > 30 000
Insufficient mouse events events.len() < 3
Insufficient mouse distance total_dist < 10.0 px
Mouse speed too high total_dist / total_time_ms > 2.0 px/ms
No mouse pauses pause_count < 1
Invalid aspect ratio ar < 0.5 or ar > 3.0
Invalid devicePixelRatio dpr ≤ 0.0 or dpr > 5.0
Zero hardwareConcurrency hardware_concurrency == 0

Hash Chain Specification

H(0) = Blake3( session_id_bytes ║ pub_key_bytes ║ salt₀_bytes )

H(n) = Blake3(
    saltₙ₋₁_bytes
  ║ H(n-1)_bytes
  ║ timestamp_u64_le_bytes
  ║ Blake3( UTF-8( JSON(entropy_data) ) )
  ║ Blake3( UTF-8( JSON(stack_state)  ) )
)

All inputs are concatenated in the order shown. timestamp is encoded as a 64-bit unsigned integer in little-endian byte order. JSON serialisation of entropy_data and stack_state uses the field order defined by the shared Rust types (serde derive, no custom ordering).


WASM API

The WASM module (antibot_wasm) exports the following functions to JavaScript:

Function Signature Description
generate_keypair() () → string Generate Ed25519 keypair; return hex public key. Private key stored in WASM memory.
get_public_key() () → string Return hex public key, or "" if not initialised.
sign_message(msg) (string) → string Sign UTF-8 string; return hex signature, or "" if not initialised.
compute_next_hash(prev, ts, entropy, stack, salt) (string, u64, string, string, string) → string Compute next Blake3 chain hash; all inputs/output hex or JSON strings.
run_program(b64) (string) → JsValue Execute base64 VM program; return { stack: u32[], ip: number }.

All functions return empty strings on error rather than panicking. Callers must check for empty return values before using the result.