# ChronoSeal API Reference ChronoSeal defines a small, deterministic API surface for browser attestation and heartbeat verification. ## Base URL All endpoints are relative to the server root. In development: `http://localhost:3000`. In production: the HTTPS origin of the protected site. --- ## POST /init Initialise a new browser session. ### Request ```http POST /init Content-Type: application/json ``` ```json { "public_key": "hex-encoded 32-byte Ed25519 verifying key" } ``` | Field | Type | Description | |---|---|---| | `public_key` | `string` | Hex-encoded 32-byte Ed25519 public key generated by the WASM runtime | ### Response `200 OK` ```json { "session_id": "64-char hex string", "salt": "32-char hex string", "opcodes_b64": "base64-encoded VM program", "initial_hash": "64-char hex string", "expires_at": 1234567890123, "heartbeat_min_interval_ms": 12000, "heartbeat_max_interval_ms": 25000, "gene_size": 512, "mutation_step": 1, "mutation_order_b64": "base64-encoded mutation program" } ``` | Field | Type | Description | |---|---|---| | `session_id` | `string` | Opaque session identifier for the current browser session | | `salt` | `string` | Random 16-byte salt used to seed the hash chain | | `opcodes_b64` | `string` | Base64-encoded randomized VM program executed on every heartbeat | | `initial_hash` | `string` | Initial chain hash `H(0)` used as `prev_hash` for the first heartbeat | | `expires_at` | `number` | Unix timestamp in milliseconds after which the session expires | | `heartbeat_min_interval_ms` | `number` | Minimum heartbeat interval in milliseconds | | `heartbeat_max_interval_ms` | `number` | Maximum heartbeat interval in milliseconds | | `gene_size` | `number` | Size of the initial synthetic gene buffer | | `mutation_step` | `number` | Initial mutation step expected on the first heartbeat | | `mutation_order_b64` | `string` | Base64-encoded mutation order for gene commitment preview | ### Error `POST /init` returns `500 Internal Server Error` only for server-side failures such as invalid public key length or persistence errors. No detailed error information is exposed to callers. --- ## POST /hb Submit a heartbeat to continue the session. ### Request ```http POST /hb Content-Type: application/json ``` ```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 } ] }, "stack_state": { "stack": [2971406957, 1234567890], "ip": 42 }, "fingerprint": { "aspectRatio": "1.7777777778", "devicePixelRatio": 2, "hardwareConcurrency": 8 }, "mutation_step": 1, "gene_commitment": "64-char hex", "signature": "128-char hex" } ``` | Field | Type | Description | |---|---|---| | `session_id` | `string` | Session ID from `/init` | | `prev_hash` | `string` | Previous hash chain head (`initial_hash` on first heartbeat) | | `timestamp` | `number` | `Date.now()` in milliseconds | | `entropy_data.events` | `array` | Mouse event list since the previous heartbeat | | `stack_state.stack` | `array` | VM stack contents after program execution | | `stack_state.ip` | `number` | VM instruction pointer after execution | | `fingerprint.aspectRatio` | `string` | `screen.width / screen.height` to 10 decimal places | | `fingerprint.devicePixelRatio` | `number` | `window.devicePixelRatio` | | `fingerprint.hardwareConcurrency` | `number` | `navigator.hardwareConcurrency || 1` | | `mutation_step` | `number` | Current mutation step sent by the client | | `gene_commitment` | `string` | Gene commitment produced by the WASM preview mutation engine | | `signature` | `string` | Hex-encoded 64-byte Ed25519 signature over the canonical payload | ### Canonical Signing Payload The client signs a canonical JSON object with top-level keys sorted alphabetically: ```json { "entropyData": { "events": [{ "t": ..., "x": ..., "y": ... }] }, "fingerprint": { "aspectRatio": "...", "devicePixelRatio": ..., "hardwareConcurrency": ... }, "geneCommitment": "...", "mutationStep": ..., "prevHash": "...", "sessionId": "...", "stackState": { "ip": ..., "stack": [...] }, "timestamp": ... } ``` Note: the signed payload uses camelCase while the transport request uses snake_case. ### Response `200 OK` — Accepted ```json { "status": "ok", "next_salt": "32-char hex string", "next_mutation_step": 2, "next_mutation_order_b64": "base64-encoded mutation program" } ``` | Field | Type | Description | |---|---|---| | `status` | `string` | Always `ok` | | `next_salt` | `string` | Next server salt for the following heartbeat | | `next_mutation_step` | `number` | Next mutation step to apply after acceptance | | `next_mutation_order_b64` | `string` | Base64-encoded next mutation program | ### Response `200 OK` — Rejected ```json { "status": "ok" } ``` A rejected heartbeat omits `next_salt`, `next_mutation_step`, and `next_mutation_order_b64`. This silent rejection model avoids giving attackers distinct failure signals. --- ## Validation Rules Heartbeats are rejected silently when any validation step fails: * session missing or expired * signature invalid * hash chain mismatch * mutation step mismatch * gene commitment mismatch * timestamp outside ±30 seconds * insufficient mouse events * insufficient mouse movement * unrealistic speed profile * missing pause intervals * invalid fingerprint values --- ## WASM Runtime Exports The WASM module exports the following functions to JavaScript: | Function | Signature | Description | |---|---|---| | `generate_keypair()` | `() -> string` | Generate a new Ed25519 keypair and return the public key hex | | `get_public_key()` | `() -> string` | Return the current public key hex | | `sign_message(msg)` | `(string) -> string` | Sign a UTF-8 string payload and return the hex signature | | `compute_next_hash(prev, ts, entropy, stack, salt)` | `(string, u64, string, string, string) -> string` | Compute the next Blake3 hash chain value | | `run_program(b64)` | `(string) -> JsValue` | Execute a base64 VM program and return stack state | | `init_gene_state(gene_size)` | `(u32) -> bool` | Initialise the synthetic gene buffer in WASM memory | | `preview_gene_commitment(order_b64)` | `(string) -> string` | Preview the next gene commitment from a mutation order | | `commit_gene_preview()` | `() -> bool` | Commit the previewed mutation after an accepted heartbeat | | `discard_gene_preview()` | `() -> void` | Discard the previewed mutation after rejection or error | | `current_gene_commitment()` | `() -> string` | Return the current committed gene commitment | String-returning functions return `""` on error. Callers must handle empty values and boolean failures gracefully.