Technical documentation
Under the hood of a TrueTake proof.
Everything a proof is made of is public and recomputable: formats, formulas, keys and ledger. This page summarises the protocol; the verifier is its reference implementation.
H(x) = SHA-256(C(x))
C(x) = UTF-8(JCS(x)) // RFC 8785
01Primitives
Standard building blocks, chosen to be easy to re-implement and audit. No home-made algorithm.
-
Ed25519
Signatures of QR codes, checkpoints, the certificate and public projections.
RFC 8032
-
SHA-256
Every hash: segments, records, chain, checkpoints, final video.
FIPS 180-4
-
JCS
Canonical JSON: the same object always gives the same bytes, hence the same hash.
RFC 8785
-
CBOR + COSE_Sign1
Compact, signed payload of the capture QR codes.
RFC 8949 · RFC 9052
-
Base45
Dense alphanumeric encoding: smaller QR codes that are easier to film.
RFC 9285
-
Merkle
A sorted binary tree that commits a whole batch of proofs and checkpoints to a single root.
truetake-merkle-1
02Signed QR codes
Every code shown by phone A is a COSE_Sign1 message signed by the server (Ed25519), Base45-encoded in an error-correction-M QR code. It holds the operation, the step number, a validity window and, from the second code on, a 16-byte link to the segment chain at the last accepted segment and to the previous QR code.
qr_(s+1).previous_link = first16(SHA-256("TrueTake-QR-link-v3\0" ‖ chain_j ‖ qr_payload_hash_s))
resume_qr.previous_link = first16(SHA-256("TrueTake-QR-resume-v1\0" ‖ closing_checkpoint_hash ‖ qr_payload_hash_(step-1)))
COSE_Sign1 = Ed25519("TrueTake-QR-v1\0" ‖ payload_cbor)
- Validity: 20 seconds
- Signature prefix:
TrueTake-QR-v1\0 - Stale codes are ignored, never accepted
03Segment chain and checkpoints
Every filmed segment produces a canonical truetake.unit record that the filming phone computes before sending it: SHA-256, size, duration and the context hash (location, sensors) when allowed. Each record is chained to the previous one. Every two minutes and at every interruption, the server signs a checkpoint that commits to the chain head, the QR sequence, its own observations and the coverage totals; checkpoints are anchored too. Notation: H(x) = SHA-256(JCS(x)).
unit_record_u = { type:"truetake.unit", version:1, kind:"segment", operation_id, span, u, step_believed, media_sha256, media_size, device_start_time, duration_ms, context_hash, run:null }
unit_hash_u = H(unit_record_u)
span_head_j = H({ type:"truetake.span-head", version:1, operation_id, span:j, qr_hash, closing_checkpoint_hash })
chain_u = H({ type:"truetake.unit-chain", version:1, operation_id, u, previous: chain_(u-1) | span_head_j, unit_hash })
qr_seq_head_s = H({ type:"truetake.qr-seq", version:1, previous, step:s, qr_payload_hash })
checkpoint_k = Ed25519("TrueTake-Proof-v1\0" ‖ C({ type:"truetake.checkpoint", k, chain_head, qr_seq_head, observations_hash, previous_checkpoint_hash, totals, … }))
The first segment of a span links to the span head, which commits to the span’s first QR code and, after an interruption, to the checkpoint that closed the previous span. Changing a single segment changes every hash after it, and the signed checkpoint no longer matches.
04The final certificate
At the end of the loop, the server assembles the video and signs the truetake.final-proof certificate (version 2). It commits to the file hash, the final checkpoint and through it the whole signed history, the chain head, the spans, the coverage totals, the capture manifest and the forensic report.
signature = Ed25519("TrueTake-Proof-v1\0" ‖ C(final_proof ∖ signature))
proof_hash = H(final_proof)
merkle_leaf = H({ type:"truetake.proof-leaf", version:1, proof_hash })
checkpoint_leaf = H({ type:"truetake.checkpoint-leaf", version:1, checkpoint_hash })
05Merkle anchoring
Certificates and checkpoints are grouped in batches. Each batch publishes a single root; each proof keeps its Merkle path to show it belongs to the batch, without revealing the others.
Node(l, r) = H({ type:"truetake.merkle-node", version:1, left:l, right:r })
tx_hash = "0x" ‖ H({ type:"truetake.simulated-anchor", version:1, batch_id, merkle_root })
- Leaves are deduplicated and sorted by byte value
- An odd node at the end of a level is duplicated
- The path says on which side each sibling sits
- A single leaf: the root is the leaf, empty path
Simulated network
Default mode of the beta. The transaction is a recomputable hash of the batch (0x + H(batch, root)), published in the public ledger. The verifier shows a “Simulated network” badge: it is valid, but it is not a public blockchain.
Test network
Optional: a real anchor(root, batch) transaction on Base Sepolia. The verifier shows “Test network”.
06Forensic heuristics
Eleven simple, documented checks, recomputed for every proof. They are consistency heuristics, not AI detection: the score is not a probability of tampering. No detector of filmed screens or generated images is active; these analyses are planned.
| Check | Measure | Rule |
|---|---|---|
qr_presence |
Share of frames where the right QR code is visible (minimum over live segments) | Pass ≥ 30%, warn ≥ 10% |
segment_duration |
Duration of each segment | Between 0.7 and 2 s |
upload_latency |
Delay between the code being issued and the segment being received | Pass ≤ 15 s, warn ≤ 50 s |
device_clock_skew |
Gap between the phone clock and the server clock | Pass ≤ 5 s, warn ≤ 30 s |
resolution_consistency |
Number of distinct resolutions | Exactly one |
codec_consistency |
Number of distinct codecs | Exactly one |
frame_rate_consistency |
Frame-rate spread | Pass ≤ 5 fps, warn ≤ 15 fps |
sequence_monotonic |
Order of steps | Strictly increasing |
context_continuity |
Segments that come with their context | All, or not evaluated |
coverage |
Share of the capture time filmed live | Informative, never a failure |
capture_integrity |
Segments whose code contradicts the sequence | Pass at zero, warn otherwise |
Decision: no failure → “no significant anomaly”; one failure → “suspicious”; two or more → “highly suspicious”. A QR or ordering check that cannot be evaluated makes the analysis “inconclusive”.
07Verification
The verifier replays thirteen ordered checks in your browser, page by page. A failure propagates to the checks that depend on it: nothing is swept under the rug.
-
01Format and cryptographic profile
schema_profile -
02Certificate signed by a published key
final_signature -
03Signed checkpoints, each linked to the previous one
checkpoint_chain -
04Every page replayed: segment chain, QR codes and their links, observations
pages -
05Live coverage recomputed against every checkpoint
coverage -
06Capture manifest and segment contexts
snapshots_and_contexts -
07Forensic report committed by the certificate
forensic_report -
08Proof hash and Merkle leaf
proof_hash_and_leaf -
09Path up to the batch root
merkle_path -
10Anchoring of the checkpoints
checkpoint_anchors -
11Hash of the video you provide
file_sha256 -
12Perceptual fingerprint (not implemented yet)
perceptual_video_hash -
13Root published in the ledger
network_contract_confirmation
08Privacy
A proof only needs the minimum. What is not needed is not collected; what is optional is asked for explicitly.
-
Optional, coarse location
Coordinates rounded to a thousandth of a degree (about 100 m), only if B allows it.
-
Reduced sensors
One root-mean-square value per segment, as integers. No raw stream.
-
Nothing on chain
The ledger only holds Merkle roots: no video, no location, no identifier.
-
Local verification
The verifier computes everything in the browser. Your files are not uploaded.
-
Limited retention
Videos and traces are deleted when they expire; see the privacy policy.