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 installCore 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);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
| Check | What 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.proofassertion can be verified - Photos with only
succinct.bindingswill failverifyProof()(they haven't been proven yet) - The Apple Root CA is pinned in the SDK; proofs must chain to this root