Provenance

Native SDK (@provenance/react-native)

Hardware-anchored capture for React Native / Expo apps. One component captures a photo, hashes it and builds the attestation bundle natively, signs it in the device's secure hardware (iOS Secure Enclave / Android StrongBox), embeds the manifest as XMP, and hands your JS only the signed artifact — raw pixels never cross the bridge.

Photo capture is production-ready; native chunked video is on the roadmap for a later native-SDK release (mode="video" returns not_implemented — the web simulator already demonstrates the video protocol).

Install

npx expo install @provenance/react-native

Add the config plugin to app.json so expo prebuild wires the camera + Face ID permissions:

{ "expo": { "plugins": ["@provenance/react-native"] } }

You can pass usage strings: ["@provenance/react-native", { "faceIDUsage": "…", "cameraUsage": "…" }]. This is a native module — build a dev client (expo run:ios --device / expo run:android --device); it does not run in Expo Go.

Enroll the device

Enrollment proves the device's silicon to the server, which assigns the authoritative hardware_tier. The client only hints — the server decides from the attestation evidence.

import { enrollDevice } from "@provenance/react-native";

const { deviceId, tier } = await enrollDevice({
  baseUrl: "https://api.provenance.io",
  apiKey: "prv_live_…", // an admin/attest-scoped key
});
// tier: "sep" | "strongbox" | "tee" | "software"

Under the hood: a single-use server challenge (/v1/admin/devices/enrollment-challenge) is bound into an Android Key Attestation chain or an iOS App Attest object, then enrolled at /v1/admin/devices.

| Result tier | When | | --- | --- | | sep | iPhone Secure Enclave (verified App Attest, INTEGRITY_CHECKS=on) | | strongbox | Android device with a StrongBox secure chip | | tee | Android TrustZone (no StrongBox) | | software | emulator / no hardware backing → scores hardware_backing_absent (+40) |

Capture

import { ProvenanceCamera } from "@provenance/react-native";

<ProvenanceCamera
  claimId="CLM-1024"
  mode="photo"
  biometricPrompt   // require a Face ID / Touch ID-gated enclave signature
  onAttest={(r) => uploadBundle(r.bundleJson, r.fileUri)}
  onError={(e) => console.warn(e.code, e.message)}
/>

onAttest yields { fileUri, attestationId, bundleJson }, the signed canonical bundle. Send bundleJson to POST /v1/claims/attest. biometric_present: true means the enclave enforced the biometric on the signature itself. If the user denies Face ID, signing fails closed and no unsigned bundle escapes.

For apps that drive their own shutter, the imperative capture({ claimId, mode }) returns the same result.

Zero-bridge media

Raw media bytes never cross the React Native bridge: configuration goes in, only signed artifacts come out. A static check (check:zero-bridge) enforces that no bridge method or DTO on either platform carries a raw byte payload. Your JS receives a file URI and the bundle — never the pixels.

Tiers, trust & video