# ZCAM SDK ## Architecture ### Overview The ZCAM SDK allows an integrating app to: 1. Take a picture 2. Generate and sign a valid C2PA manifest over the photo (including a signature using the device key) including bindings to generate an SP1 proof 3. Generate a verifiable SP1 proof that the signature is valid, was signed using a key bound to the app, and corresponds to the photo, attaching that to a valid C2PA manifest The core flow uses cryptographic keys stored on the device's Secure Enclave to sign and attest that a photo was taken using the device's camera and has not been tampered with. ### Cryptographic Keys The ZCAM architecture generates and leverages two keys: | Key | Purpose | Apple Attested | | --------------- | ------------------------------ | -------------- | | **Device Key** | Signs over a hash of the photo | Yes | | **Content Key** | Signs the C2PA manifest | No | Both keys live in the Secure Enclave of the iPhone device. Importantly, the device key is specifically attested to have been generated on the Secure Enclave and is scoped to the specific app. ### Photo Lifecycle A photo progresses through three states to become a verified zphoto: | State | C2PA Assertion | Description | | --------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | **Raw** | None | Original photo taken by phone, including metadata. No verifiable, attested traits. | | **Provable** | `succinct.bindings` | Embeds a C2PA manifest including required inputs for generating a zero knowledge proof. This includes a signature using an Apple attested key. | | **Verified (zphoto)** | `succinct.proof` | Embeds a C2PA manifest including a verifiable zero knowledge proof that the photo was signed using a valid Apple attested key. | ``` Raw Photo → [Capture] → Provable Photo → [Prove] → Verified zphoto (bindings) (proof) ``` ### Flow #### 1. Initialization The SDK first gets initialized. The main logic here is to generate or load the two necessary keys (device key and content key). #### 2. Capture The capture step allows a user to take a photo and output a *provable* photo. When a user wants to take a zphoto, the SDK opens the camera screen. After the photo is taken, the SDK embeds a valid C2PA manifest by doing the following: 1. The photo is hashed, then signed using the device key. This outputs an Apple Attest assertion including a signature over the hash. 2. The C2PA manifest is initialized with a *capture* action including metadata: time of photo, OS, device model, software version, etc. 3. A `succinct.bindings` assertion is added to the manifest including data necessary to prove that the photo was signed using a valid Apple attested key. ![Capture flow diagram](/images/diagram_capture.png) **Output:** A provable photo with a valid C2PA manifest containing metadata and an assertion that the photo was signed with an Apple attested key. #### 3. Prove The prove step takes the provable photo with C2PA manifest including a `succinct.bindings` assertion and generates a proof verifying its validity. This is done by using the SP1 Prover Network to prove the ZCAM program with the photo (and C2PA manifest) as input. The SDK receives the proof from the network, then updates the photo and C2PA manifest: * The `succinct.bindings` assertion is removed * A `succinct.proof` assertion is added containing the verifiable proof ![Capture flow diagram](/images/diagram_prove.png) **Output:** The final zphoto with a valid C2PA manifest including a verifiable proof. #### 4. Verify The SDK includes logic for parsing and verifying the proof from the C2PA manifest: * Extract the manifest from the photo * Verify the proof in the manifest * Verify the photo hash matches the manifest ## FAQs *TODO* ## Security ### Security Claims A verified zphoto provides the following guarantees: 1. **Device Authenticity**: The photo was signed on a genuine Apple device with a Secure Enclave 2. **App Binding**: The signing key is bound to a specific application via Apple App Attest 3. **Content Integrity**: The photo has not been modified since the signature was created 4. **Proof Validity**: The above properties are verified in a zero-knowledge proof that chains to Apple's root certificate ### Trust Assumptions The security of ZCAM relies on the following assumptions: | Assumption | Rationale | | ----------------------------------------- | ------------------------------------------------------------- | | Secure Enclave keys cannot be exfiltrated | Hardware security guarantee from Apple | | Apple App Attest is honest | Apple signs attestations only for legitimate device/app pairs | | SP1 proof system is sound | Groth16 proofs cannot be forged without the witness | | SDK code integrity | The SDK code on-device has not been tampered with | ### Critical Assumption: Capture-to-Sign Integrity :::warning The SDK signs the photo immediately after capture, but there is no cryptographic binding between the camera hardware and the signing operation. We assume no malicious code can execute between the moment the photo is captured and when it is signed. ::: This assumption holds if: * The SDK code has not been tampered with * The host application does not inject code into the capture flow * The device is not compromised This is an **operational security** assumption, not a cryptographic guarantee. ### Open Issues #### App Trust Model Currently, the SP1 program verifies that a photo was signed by *an* Apple Attested key, but does not restrict *which* apps are trusted. A malicious app could: 1. Integrate the SDK 2. Feed AI-generated images to the signing flow 3. Generate valid proofs for inauthentic photos **Mitigations under consideration:** * Maintain a whitelist of trusted App IDs verified in the proof * Output the App ID as a public value, allowing verifiers to check trust * Require app attestation/audit before whitelist inclusion #### Third-Party SDK Integration As the SDK scales to third-party apps, we need mechanisms to ensure integrators use the SDK as intended. Options include: * Code signing / integrity checks on the SDK binary * Runtime attestation of the SDK version * Legal/contractual requirements for integrators #### SDK Code Integrity We need to guarantee the SDK code cannot be tampered with: * **Build pipeline**: CI/CD must produce reproducible, auditable builds * **On-device**: The SDK binary should be verified before execution * **Updates**: SDK updates must be authenticated This is not yet fully implemented. ### Inauthentic Photos Even with cryptographic guarantees that a photo was captured by a genuine device running the SDK, attackers may attempt physical attacks: * **Screen capture**: Photographing a screen displaying an AI-generated or manipulated image * **Printed photo capture**: Photographing a printed image * **Projector capture**: Photographing a projected image These attacks bypass software-level protections because the camera genuinely captures "something" — just not an authentic scene. Note that the taken photo is still "valid" in that it was taken using the camera app, properly signed etc. But the photo is still "inauthentic" in terms of what it is trying to portray. This is a different, much harder problem to solve. In fact, a cryptographic solution is likely impossible. Instead, our strategy is to provide as much contextual data in the metadata for a user to be able to make an informed decision on the authenticity of the photo. #### Detection Signals The following metadata fields can help detect physical replay attacks: | Field | What It Measures | Fraud Indicator | | ------------------------------------ | --------------------------------------- | ------------------------------------------------------------------------------------------ | | **GPS / Location** | Location | Location inconsistent with claimed scene | | **Timestamp** | Time | Time inconsistent with lighting conditions | | **Subject Distance** | Distance to focused subject | Focus distance inconsistent with the subject of picture | | **Depth Map** | Per-pixel depth from dual cameras/LiDAR | Flat depth when the subject has more depth variance | | **White Balance** | Color temperature | Screen color temperature differs from natural light | | **Ambient Light** | Environmental light level | Unusually uniform lighting could indicate screen glow | | **Focal Length ?** | Lens focal length used | Very short focal lengths (wide angle close-up) suggest photographing a nearby flat surface | | \*\*Rolling Shutter Artifacts ? \*\* | Screen refresh banding | Horizontal bands indicate screen capture (refresh rate mismatch) | | \*\*Moiré Patterns ? \*\* | Interference patterns | Grid/wave patterns from screen pixels or print halftones | | \*\*Exposure Settings ? \*\* | Shutter speed, ISO, aperture | Unusual combinations for indoor screen photography | | \*\*Lens Distortion ? \*\* | Optical distortion profile | Comparing actual vs expected distortion for lens/distance | #### Implementation Status :::note These detection signals are not currently implemented in the SDK. They represent potential future enhancements to detect physical replay attacks. ::: ## ZK Proof Details The SP1 program proves the following: 1. The photo bytes matches the expected bytes in the C2PA manifest 2. The Apple Attest attestation is valid 3. The assertion (i.e. signature of the photo hash using the attested key) is valid The photo bytes are provided as input to the proof. ### Proof Logic | Step | Operation | Guarantees | | ---- | -------------------------------------------- | ----------------------------------------- | | 1 | Extract manifest from photo bytes | — | | 2 | Extract bindings and data hash from manifest | — | | 3 | Compute photo hash | — | | 4 | Check data hash == photo hash | Manifest corresponds to this photo | | 5 | Validate attestation | The attestation is valid and from Apple | | 6 | Validate assertion | Photo hash was signed by the attested key | ### Attestation Validation Guarantees: * The device key was generated in a genuine Apple Secure Enclave * The key is bound to a specific app (via `app_id` / RP ID) * The device is running a legitimate app (AAGUID = `appattest` or `appattestdevelop`) Validates: * Certificate chain verifies up to Apple Root CA * Nonce matches the challenge * Public key hash matches the key ID * RP ID == SHA256(app\_id) * AAGUID is a valid Apple attestation identifier ### Assertion Validation Guarantees: * The photo corresponding to this hash was signed using the ZCAM SDK Validates: * ECDSA signature over the photo hash using the public key from attestation ### Public Outputs * Photo hash (SHA-256) * Apple Root CA certificate These committed values allow verifiers to confirm the proof corresponds to a specific photo and chains to Apple's actual root certificate. ## Capture SDK The Capture SDK (`react-native-zcam1-capture`) takes photos and produces C2PA-signed assets bound to hardware-backed integrity signals. ### Installation ```bash npm i react-native-zcam1-capture cd ios && pod install ``` ### Core Concepts The Capture SDK: * Renders a native iOS camera preview via `ZCamera` * Generates App Attest assertions over photo hashes * Embeds C2PA manifests with `succinct.bindings` assertion * Returns signed photos ready for proof generation ### API Reference #### `initCapture(settings)` Initializes device keys and attestation. Call once on app startup. ```tsx import { initCapture, CaptureInfo, Settings } from "react-native-zcam1-capture"; const settings: Settings = { appId: "TEAM_ID.com.example.app", production: false, }; const captureInfo: CaptureInfo = await initCapture(settings); ``` **Parameters:** * `settings.appId` - Your Team ID + Bundle ID * `settings.production` - `false` for development, `true` for production **Returns:** `CaptureInfo` containing: * `appId` - The app identifier * `deviceKeyId` - App Attest device key ID * `contentPublicKey` - Secure Enclave content key * `contentKeyId` - Derived content key identifier * `attestation` - App Attest attestation blob #### `ZCamera` React component that renders the native camera preview. ```tsx import { useRef } from "react"; import { ZCamera, CaptureInfo } from "react-native-zcam1-capture"; function CameraScreen({ captureInfo }: { captureInfo: CaptureInfo }) { const camera = useRef(null); return ( ); } ``` **Props:** * `captureInfo` (required) - From `initCapture()` * `position` - Camera to use: `"front"` or `"back"` (default: `"back"`) * `isActive` - Whether camera is running (default: `true`) * `style` - Style for the camera view #### `ZCamera.takePhoto()` Captures a photo and returns a signed `ZPhoto`. ```tsx const photo = await camera.current?.takePhoto(); console.log(photo.originalPath); // Raw capture path console.log(photo.path); // C2PA-signed photo path ``` **Returns:** `ZPhoto` with: * `originalPath` - Path to the raw captured image * `path` - Path to the C2PA-signed image with `succinct.bindings` ### Complete Example ```tsx import { useEffect, useRef, useState } from "react"; import { View, Button, Text, StyleSheet } from "react-native"; import { SafeAreaView } from "react-native-safe-area-context"; import { initCapture, ZCamera, CaptureInfo } from "react-native-zcam1-capture"; import { FileSystem, Dirs } from "react-native-file-access"; export function CaptureScreen() { const camera = useRef(null); const [captureInfo, setCaptureInfo] = useState(); const [lastPhoto, setLastPhoto] = useState(); useEffect(() => { const settings = { appId: process.env.EXPO_PUBLIC_APP_ID!, production: false, }; initCapture(settings).then(setCaptureInfo); }, []); const handleCapture = async () => { const photo = await camera.current?.takePhoto(); if (!photo) return; // Save to app's document directory const destPath = `${Dirs.DocumentDir}/photos/${Date.now()}.jpg`; await FileSystem.mkdir(`${Dirs.DocumentDir}/photos`); await FileSystem.cp(photo.path, destPath); setLastPhoto(destPath); }; if (!captureInfo) { return ( Initializing camera... ); } return (