Video attestation
Video can't be hashed as one blob during live capture, and MP4/WebM containers have no clean XMP-equivalent for embedding. So video attestation makes two fixed deviations from the image flow.
Chunk hash chain
The recording is segmented into fixed-duration chunks (target 2s). Each chunk hashes into a chain:
chunk_hash_i = SHA256( prev_chunk_hash || SHA256(chunk_bytes) )
The chain seed (prev_chunk_hash for chunk 0) is:
- the server-issued
session_noncefor live sessions, or ""for recorded/uploaded video.
This makes the chain tamper-evident: altering, dropping, or reordering any chunk breaks every subsequent hash (frame_hash_mismatch, +50). sensor_hash for video is the final chain hash, so all downstream logic works unchanged.
Sidecar manifest
The bundle is stored and transmitted alongside the video, keyed by attestation_id and the final chain hash:
"video_manifest": {
"chunk_duration_ms": 2000,
"duration_seconds": 14.2,
"codec": "video/webm;codecs=vp9",
"chunks": [ { "index": 0, "byte_length": 183211, "chunk_hash": "…", "captured_at": "…" } ]
}
Container-embedded manifests (MP4 boxes / C2PA video) are on the roadmap; today manifests travel as a sidecar only.
Two flows, one mechanism
- Live capture session — the client opens a session, streams chunk hashes as they're captured, and the server timestamps each arrival. This enables the timing-based liveness checks. Finalize with the signed bundle.
- Recorded / uploaded video — the full manifest and signed bundle are submitted in one shot to
/v1/claims/attest. Verification is identical, except liveness checks don't apply (liveness_checked: false).
Replay resistance
For live sessions, the session_nonce is the primary anti-replay defense: a pre-recorded, pre-hashed video can't produce a valid chain because its seed can't contain a nonce the server only just generated. A mismatch scores session_nonce_mismatch (+70).
What liveness_check_failed does and does NOT mean
liveness_check_failed (+40) is a secondary heuristic layered on the nonce check. It is rule-based timing/continuity only: inter-chunk arrival gaps far outside the expected cadence, non-monotonic client timestamps, wall-clock vs claimed-duration drift, or chunks arriving after finalize.
It is not biometric liveness. Real liveness detection (challenge-response, texture analysis) is ML and not part of the current release. Don't read this factor as "we confirmed a live human" — read it as "the chunk timing was inconsistent."