Sidecar Format
The sidecar file contains both layers of cryptographic proof in a single JSON document that travels alongside the media file.
Overview
A sidecar file (e.g., photo.sidecar.json) accompanies each media file (e.g., photo.jpg). This separation keeps the original media untouched while providing all verification data.
Why JSON?
The proof format is JSON for three reasons:
- Human-readable — Developers can inspect proofs without special tools
- Flexible storage — Store as a file, in a database, on a blockchain, or transmit via API
- Easy integration — Every platform has JSON libraries
Structure
{
"version": "1.0",
"capture_trust": {
"jwt": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ii4uLiJ9..."
},
"media_integrity": {
"content_hash": "a1b2c3d4e5f6...",
"signature": "MEUCIQC...",
"public_key": "BHx5y...",
"capture_id": "550e8400-e29b-41d4-a716-446655440000",
"captured_at": "2025-01-15T10:30:00Z"
}
}
Fields
Root
| Field | Type | Description |
|---|---|---|
version | string | Schema version (currently "1.0") |
capture_trust | object | Server-issued trust token |
media_integrity | object | Device-generated integrity proof |
capture_trust
| Field | Type | Description |
|---|---|---|
jwt | string | ES256-signed JWT from the SignedShot API |
media_integrity
| Field | Type | Description |
|---|---|---|
content_hash | string | SHA-256 hash of the media file (hex, 64 characters) |
signature | string | ECDSA signature over the signed message (base64) |
public_key | string | Device's public key (base64, uncompressed EC point, 65 bytes) |
capture_id | string | UUID of the capture session (must match JWT) |
captured_at | string | ISO8601 UTC timestamp of capture |
JWT Payload
The capture_trust.jwt is a standard JWT. When decoded, it contains:
{
"iss": "https://api.signedshot.io",
"aud": "signedshot",
"sub": "capture-service",
"iat": 1705312200,
"capture_id": "550e8400-e29b-41d4-a716-446655440000",
"publisher_id": "9a5b1062-a8fe-4871-bdc1-fe54e96cbf1c",
"device_id": "ea5c9bfe-6bbc-4ee2-b82d-0bcfcc185ef1",
"attestation": {
"method": "app_check",
"app_id": "io.signedshot.capture"
}
}
JWT Fields
| Field | Type | Description |
|---|---|---|
iss | string | Issuer (API URL) |
aud | string | Audience |
sub | string | Subject (always "capture-service") |
iat | number | Issued at (Unix timestamp) |
capture_id | string | Unique capture session ID |
publisher_id | string | Publisher UUID |
device_id | string | Device UUID |
attestation | object | Attestation details |
attestation Object
| Field | Type | Description |
|---|---|---|
method | string | "sandbox", "app_check", or "app_attest" |
app_id | string | App bundle ID (only present when attested) |
Signature Format
Media Integrity Signature
The media_integrity.signature signs a message constructed from:
{content_hash}:{capture_id}:{captured_at}
For example:
a1b2c3d4e5f6...:550e8400-e29b-41d4-a716-446655440000:2025-01-15T10:30:00Z
The signature is:
- Algorithm: ECDSA with P-256 curve
- Generated by the device's Secure Enclave
- Encoded as base64
JWT Signature
The JWT uses:
- Algorithm: ES256 (ECDSA with P-256)
- Key ID (
kid) in header for JWKS lookup - Verification via
/.well-known/jwks.json
File Naming Convention
| Media File | Sidecar File |
|---|---|
photo.jpg | photo.sidecar.json |
video.mp4 | video.sidecar.json |
IMG_1234.HEIC | IMG_1234.sidecar.json |
Example: Complete Sidecar
{
"version": "1.0",
"capture_trust": {
"jwt": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtleS0xIn0.eyJpc3MiOiJodHRwczovL2FwaS5zaWduZWRzaG90LmlvIiwiYXVkIjoic2lnbmVkc2hvdCIsInN1YiI6ImNhcHR1cmUtc2VydmljZSIsImlhdCI6MTcwNTMxMjIwMCwiY2FwdHVyZV9pZCI6IjU1MGU4NDAwLWUyOWItNDFkNC1hNzE2LTQ0NjY1NTQ0MDAwMCIsInB1Ymxpc2hlcl9pZCI6IjlhNWIxMDYyLWE4ZmUtNDg3MS1iZGMxLWZlNTRlOTZjYmYxYyIsImRldmljZV9pZCI6ImVhNWM5YmZlLTZiYmMtNGVlMi1iODJkLTBiY2ZjYzE4NWVmMSIsImF0dGVzdGF0aW9uIjp7Im1ldGhvZCI6ImFwcF9jaGVjayIsImFwcF9pZCI6ImlvLnNpZ25lZHNob3QuY2FwdHVyZSJ9fQ.signature"
},
"media_integrity": {
"content_hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"signature": "MEUCIQDKZokqnCjrRtw+3S0P2mjJH+E8zRqgaG6R4bG6V7oONwIgF3lQsGV1K3K1K3K1K3K1K3K1K3K1K3K1K3K1K3K1K3M=",
"public_key": "BHx5yK3K1K3K1K3K1K3K1K3K1K3K1K3K1K3K1K3K1K3K1K3K1K3K1K3K1K3K1K3K1K3K1K3K1K3K1K3K1K3K1K3K1K3K1K3M=",
"capture_id": "550e8400-e29b-41d4-a716-446655440000",
"captured_at": "2025-01-15T10:30:00Z"
}
}
Verification Steps
To verify a sidecar:
- Parse the sidecar JSON
- Verify Capture Trust (JWT)
- Fetch JWKS from issuer's
/.well-known/jwks.json - Find key by
kidin JWT header - Verify ES256 signature
- Fetch JWKS from issuer's
- Verify Media Integrity
- Compute SHA-256 of media file
- Compare with
content_hash - Reconstruct signed message:
{hash}:{capture_id}:{captured_at} - Verify ECDSA signature using
public_key
- Cross-validate
- Confirm
capture_idmatches in both JWT and media_integrity
- Confirm
Next Steps
- Two-Layer Trust Model — Understand why both layers are needed
- Python Validation — Verify sidecars programmatically