Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Verify SDK

The Verify SDK (react-native-zcam1-verify) validates the authenticity of C2PA-signed photos with embedded proofs.

Installation

npm i react-native-zcam1-verify
cd ios && pod install

Core Concepts

The Verify SDK:

  • Extracts C2PA manifests from photos
  • Verifies the photo hash matches the manifest
  • Validates the ZK proof against a pinned Apple Root CA
  • Confirms the photo was taken on a genuine iOS device

API Reference

VerifiableFile

Class for verifying photo authenticity.

import { VerifiableFile } from "react-native-zcam1-verify";
 
const file = new VerifiableFile(photoUri);
Constructor:
  • uriOrPath - File path or URI to the photo

VerifiableFile.verifyHash()

Checks that the photo's content hash matches the hash recorded in the C2PA manifest.

const isHashValid: boolean = file.verifyHash();

Returns: true if the file contents haven't been tampered with

VerifiableFile.verifyProof()

Validates the embedded ZK proof against the photo hash and Apple Root CA.

const isProofValid: boolean = file.verifyProof();

Returns: true if the proof is valid

Complete Example

import { useState } from "react";
import { View, Button, Text, StyleSheet } from "react-native";
import { launchImageLibrary } from "react-native-image-picker";
import { VerifiableFile } from "react-native-zcam1-verify";
 
type VerificationResult = {
  hashValid: boolean;
  proofValid: boolean;
  authentic: boolean;
};
 
export function VerifyScreen() {
  const [result, setResult] = useState<VerificationResult>();
  const [error, setError] = useState<string>();
 
  const handlePickAndVerify = async () => {
    setResult(undefined);
    setError(undefined);
 
    const response = await launchImageLibrary({
      mediaType: "photo",
      selectionLimit: 1,
    });
 
    const asset = response.assets?.[0];
    if (!asset?.uri) return;
 
    try {
      const file = new VerifiableFile(asset.uri);
 
      const hashValid = file.verifyHash();
      const proofValid = file.verifyProof();
 
      setResult({
        hashValid,
        proofValid,
        authentic: hashValid && proofValid,
      });
    } catch (err: any) {
      setError(err.message);
    }
  };
 
  return (
    <View style={styles.container}>
      <Button title="Select Photo to Verify" onPress={handlePickAndVerify} />
 
      {result && (
        <View style={styles.result}>
          <Text style={styles.title}>
            {result.authentic ? "✓ Authentic" : "✗ Not Authentic"}
          </Text>
          <Text>Hash Valid: {result.hashValid ? "Yes" : "No"}</Text>
          <Text>Proof Valid: {result.proofValid ? "Yes" : "No"}</Text>
        </View>
      )}
 
      {error && <Text style={styles.error}>Error: {error}</Text>}
    </View>
  );
}
 
const styles = StyleSheet.create({
  container: { flex: 1, padding: 16 },
  result: { marginTop: 24, padding: 16, backgroundColor: "#f0f0f0", borderRadius: 8 },
  title: { fontSize: 18, fontWeight: "bold", marginBottom: 8 },
  error: { marginTop: 16, color: "red" },
});

Verification Helper

Create a reusable verification function:

import { VerifiableFile } from "react-native-zcam1-verify";
 
export type VerificationStatus =
  | { status: "authentic" }
  | { status: "invalid_hash" }
  | { status: "invalid_proof" }
  | { status: "no_manifest" }
  | { status: "error"; message: string };
 
export function verifyPhoto(uri: string): VerificationStatus {
  try {
    const file = new VerifiableFile(uri);
 
    if (!file.verifyHash()) {
      return { status: "invalid_hash" };
    }
 
    if (!file.verifyProof()) {
      return { status: "invalid_proof" };
    }
 
    return { status: "authentic" };
  } catch (error: any) {
    if (error.message?.includes("no manifest")) {
      return { status: "no_manifest" };
    }
    return { status: "error", message: error.message };
  }
}

What Verification Checks

CheckWhat It Validates
verifyHash()Photo bytes match the SHA-256 hash in the C2PA manifest
verifyProof()ZK proof is valid and chains to the Apple Root CA

Both checks must pass for a photo to be considered authentic.

Notes

  • Verification is performed locally—no network required
  • Only photos with succinct.proof assertion can be verified
  • Photos with only succinct.bindings will fail verifyProof() (they haven't been proven yet)
  • The Apple Root CA is pinned in the SDK; proofs must chain to this root