diff options
| author | rabble <evan@protest.net> | 2025-11-04 18:15:22 +1300 |
|---|---|---|
| committer | rabble <evan@protest.net> | 2025-11-04 18:15:22 +1300 |
| commit | d44476eee8c7b1df4ed99c760dc2a3c7874edc9a (patch) | |
| tree | 322414c09d6f44a7c61fa83fd6d17f9e26f142e3 | |
| parent | 62f0b14ae81fd062bb84791bca698b29f6b573a3 (diff) | |
draft of the proofmode spec
| -rw-r--r-- | XX.md | 358 |
1 files changed, 358 insertions, 0 deletions
| @@ -0,0 +1,358 @@ | |||
| 1 | # NIP-XX: ProofMode - Cryptographic Video Verification | ||
| 2 | |||
| 3 | `draft` `optional` | ||
| 4 | |||
| 5 | ## Abstract | ||
| 6 | |||
| 7 | This NIP defines a standard for attaching cryptographic proof manifests to video events (NIP-71) to enable verification of video authenticity, recording continuity, and device integrity. ProofMode allows viewers to verify that a video was recorded on a specific device at a specific time without editing or tampering. | ||
| 8 | |||
| 9 | ## Motivation | ||
| 10 | |||
| 11 | Social media platforms are increasingly vulnerable to deepfakes, edited videos, and synthetic media. While blockchain timestamping exists, it doesn't prove video continuity or prevent frame-level manipulation. ProofMode solves this by: | ||
| 12 | |||
| 13 | 1. **Frame-level verification** - SHA256 hashes of captured frames prove recording continuity | ||
| 14 | 2. **Hardware attestation** - iOS App Attest and Android Play Integrity verify the recording device | ||
| 15 | 3. **Cryptographic signing** - PGP signatures ensure manifest authenticity | ||
| 16 | 4. **Tamper detection** - Any edit to the video invalidates the proof chain | ||
| 17 | 5. **Segment tracking** - Recording pauses are documented with sensor data | ||
| 18 | |||
| 19 | ## Specification | ||
| 20 | |||
| 21 | ### Event Tags | ||
| 22 | |||
| 23 | ProofMode data is attached to video events (typically Kind 34236) using the following tags: | ||
| 24 | |||
| 25 | #### Required Tags | ||
| 26 | |||
| 27 | - `["verification", "<level>"]` - Verification level (see Verification Levels below) | ||
| 28 | - `["proofmode", "<manifest_json>"]` - Complete ProofManifest as compact JSON | ||
| 29 | |||
| 30 | #### Optional Tags | ||
| 31 | |||
| 32 | - `["device_attestation", "<token>"]` - Hardware attestation token from iOS App Attest or Android Play Integrity | ||
| 33 | - `["pgp_fingerprint", "<fingerprint>"]` - PGP public key fingerprint used to sign the manifest | ||
| 34 | |||
| 35 | ### Verification Levels | ||
| 36 | |||
| 37 | The `verification` tag indicates the strength of cryptographic proof: | ||
| 38 | |||
| 39 | - `verified_mobile` - Highest level: has device attestation + PGP signature + complete manifest | ||
| 40 | - `verified_web` - Medium level: has PGP signature + complete manifest (no hardware attestation) | ||
| 41 | - `basic_proof` - Low level: has proof data but no cryptographic signature | ||
| 42 | - `unverified` - No meaningful proof data | ||
| 43 | |||
| 44 | ### ProofManifest Structure | ||
| 45 | |||
| 46 | The `proofmode` tag contains a JSON object with the following structure: | ||
| 47 | |||
| 48 | ```json | ||
| 49 | { | ||
| 50 | "sessionId": "<unique_session_id>", | ||
| 51 | "challengeNonce": "<16_char_nonce>", | ||
| 52 | "vineSessionStart": "<ISO8601_timestamp>", | ||
| 53 | "vineSessionEnd": "<ISO8601_timestamp>", | ||
| 54 | "totalDuration": 6500, | ||
| 55 | "recordingDuration": 6000, | ||
| 56 | "segments": [ | ||
| 57 | { | ||
| 58 | "segmentId": "<segment_id>", | ||
| 59 | "startTime": "<ISO8601_timestamp>", | ||
| 60 | "endTime": "<ISO8601_timestamp>", | ||
| 61 | "duration": 3000, | ||
| 62 | "frameHashes": [ | ||
| 63 | "<sha256_hash_1>", | ||
| 64 | "<sha256_hash_2>", | ||
| 65 | "..." | ||
| 66 | ], | ||
| 67 | "frameTimestamps": [ | ||
| 68 | "<ISO8601_timestamp_1>", | ||
| 69 | "<ISO8601_timestamp_2>", | ||
| 70 | "..." | ||
| 71 | ], | ||
| 72 | "sensorData": { | ||
| 73 | "accelerometer": {"x": 0.1, "y": 0.2, "z": 9.8}, | ||
| 74 | "gyroscope": {"x": 0.01, "y": 0.02, "z": 0.01} | ||
| 75 | } | ||
| 76 | } | ||
| 77 | ], | ||
| 78 | "pauseProofs": [ | ||
| 79 | { | ||
| 80 | "startTime": "<ISO8601_timestamp>", | ||
| 81 | "endTime": "<ISO8601_timestamp>", | ||
| 82 | "duration": 500, | ||
| 83 | "sensorData": { | ||
| 84 | "timestamp": "<ISO8601_timestamp>", | ||
| 85 | "accelerometer": {"x": 0.1, "y": 0.2, "z": 9.8}, | ||
| 86 | "gyroscope": {"x": 0.01, "y": 0.02, "z": 0.01}, | ||
| 87 | "magnetometer": {"x": 45.0, "y": 12.0, "z": -30.0}, | ||
| 88 | "light": 150.0 | ||
| 89 | }, | ||
| 90 | "interactions": [ | ||
| 91 | { | ||
| 92 | "timestamp": "<ISO8601_timestamp>", | ||
| 93 | "interactionType": "touch", | ||
| 94 | "coordinates": {"x": 180, "y": 640}, | ||
| 95 | "pressure": 0.5 | ||
| 96 | } | ||
| 97 | ] | ||
| 98 | } | ||
| 99 | ], | ||
| 100 | "interactions": [ | ||
| 101 | { | ||
| 102 | "timestamp": "<ISO8601_timestamp>", | ||
| 103 | "interactionType": "start|stop|touch", | ||
| 104 | "coordinates": {"x": 180, "y": 640}, | ||
| 105 | "pressure": 0.5, | ||
| 106 | "metadata": {} | ||
| 107 | } | ||
| 108 | ], | ||
| 109 | "finalVideoHash": "<sha256_hash_of_complete_video>", | ||
| 110 | "deviceAttestation": { | ||
| 111 | "token": "<platform_specific_attestation_token>", | ||
| 112 | "platform": "iOS|Android|Web", | ||
| 113 | "deviceId": "<device_identifier>", | ||
| 114 | "isHardwareBacked": true, | ||
| 115 | "createdAt": "<ISO8601_timestamp>", | ||
| 116 | "challenge": "<challenge_nonce>", | ||
| 117 | "metadata": { | ||
| 118 | "attestationType": "app_attest|play_integrity|fallback", | ||
| 119 | "deviceInfo": { | ||
| 120 | "platform": "iOS", | ||
| 121 | "model": "iPhone 15 Pro", | ||
| 122 | "version": "17.0", | ||
| 123 | "manufacturer": "Apple" | ||
| 124 | } | ||
| 125 | } | ||
| 126 | }, | ||
| 127 | "pgpSignature": { | ||
| 128 | "signature": "-----BEGIN PGP SIGNATURE-----\n...\n-----END PGP SIGNATURE-----", | ||
| 129 | "publicKey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n...\n-----END PGP PUBLIC KEY BLOCK-----", | ||
| 130 | "publicKeyFingerprint": "1A2B3C4D5E6F7890..." | ||
| 131 | } | ||
| 132 | } | ||
| 133 | ``` | ||
| 134 | |||
| 135 | ### Field Descriptions | ||
| 136 | |||
| 137 | #### Core Fields | ||
| 138 | - `sessionId` - Unique identifier for the recording session | ||
| 139 | - `challengeNonce` - Random nonce generated at session start, used in device attestation to prevent replay attacks | ||
| 140 | - `vineSessionStart` / `vineSessionEnd` - Recording session boundaries | ||
| 141 | - `totalDuration` - Total elapsed time in milliseconds (including pauses) | ||
| 142 | - `recordingDuration` - Actual recording time in milliseconds (excluding pauses) | ||
| 143 | |||
| 144 | #### Segments | ||
| 145 | Recording can be paused and resumed, creating multiple segments. Each segment contains: | ||
| 146 | - `segmentId` - Unique segment identifier | ||
| 147 | - `startTime` / `endTime` - Segment boundaries | ||
| 148 | - `frameHashes` - Array of SHA256 hashes of captured video frames | ||
| 149 | - `frameTimestamps` - Timestamps when each frame was captured (optional) | ||
| 150 | - `sensorData` - Device sensor readings during recording (optional) | ||
| 151 | |||
| 152 | #### Pause Proofs | ||
| 153 | When recording is paused, sensor data is collected to prove device continuity: | ||
| 154 | - `startTime` / `endTime` - Pause boundaries | ||
| 155 | - `sensorData` - Sensor readings during pause (accelerometer, gyroscope, magnetometer, light) | ||
| 156 | - `interactions` - User touch/tap events during pause | ||
| 157 | |||
| 158 | #### Interactions | ||
| 159 | User interactions recorded throughout the session: | ||
| 160 | - `timestamp` - When interaction occurred | ||
| 161 | - `interactionType` - Type of interaction (start, stop, touch) | ||
| 162 | - `coordinates` - Screen coordinates of interaction | ||
| 163 | - `pressure` - Touch pressure (optional) | ||
| 164 | |||
| 165 | #### Final Video Hash | ||
| 166 | - `finalVideoHash` - SHA256 hash of the complete rendered video file | ||
| 167 | |||
| 168 | #### Device Attestation | ||
| 169 | Platform-specific hardware attestation proving the device is genuine: | ||
| 170 | - **iOS**: Uses App Attest API (iOS 14+) | ||
| 171 | - **Android**: Uses Play Integrity API | ||
| 172 | - **Web/Other**: Fallback software attestation | ||
| 173 | |||
| 174 | Fields: | ||
| 175 | - `token` - Platform-specific attestation token | ||
| 176 | - `platform` - Operating system (iOS, Android, Web) | ||
| 177 | - `deviceId` - Device identifier | ||
| 178 | - `isHardwareBacked` - Whether attestation uses hardware security module | ||
| 179 | - `challenge` - Challenge nonce used in attestation (matches `challengeNonce`) | ||
| 180 | - `metadata` - Platform-specific attestation details | ||
| 181 | |||
| 182 | #### PGP Signature | ||
| 183 | Cryptographic signature of the entire manifest: | ||
| 184 | - `signature` - PGP signature in ASCII-armored format | ||
| 185 | - `publicKey` - PGP public key in ASCII-armored format | ||
| 186 | - `publicKeyFingerprint` - Key fingerprint for quick lookup | ||
| 187 | |||
| 188 | ## Implementation | ||
| 189 | |||
| 190 | ### Recording Phase | ||
| 191 | |||
| 192 | 1. **Start Session** | ||
| 193 | - Generate unique `sessionId` and `challengeNonce` | ||
| 194 | - Request hardware device attestation with challenge nonce | ||
| 195 | - Initialize ProofMode session | ||
| 196 | |||
| 197 | 2. **Capture Frames** | ||
| 198 | - During recording, periodically capture video frames | ||
| 199 | - Generate SHA256 hash of each frame | ||
| 200 | - Store frame hashes with timestamps | ||
| 201 | - Optionally collect sensor data (accelerometer, gyroscope, etc.) | ||
| 202 | |||
| 203 | 3. **Handle Pauses** | ||
| 204 | - When recording pauses, stop current segment | ||
| 205 | - Begin collecting pause proof data (sensor readings, interactions) | ||
| 206 | - When resuming, start new segment | ||
| 207 | |||
| 208 | 4. **Finalize Session** | ||
| 209 | - Stop recording and close final segment | ||
| 210 | - Hash complete video file | ||
| 211 | - Compile ProofManifest with all segments, pauses, and interactions | ||
| 212 | - Sign manifest with PGP private key | ||
| 213 | - Attach ProofManifest to video event as tags | ||
| 214 | |||
| 215 | ### Verification Phase | ||
| 216 | |||
| 217 | To verify a ProofMode video, clients should: | ||
| 218 | |||
| 219 | 1. **Extract ProofManifest** | ||
| 220 | - Parse `proofmode` tag from video event | ||
| 221 | - Extract `deviceAttestation` and `pgpSignature` from separate tags | ||
| 222 | |||
| 223 | 2. **Verify PGP Signature** | ||
| 224 | - Extract PGP public key from manifest | ||
| 225 | - Verify signature of manifest JSON | ||
| 226 | - Check public key fingerprint matches `pgp_fingerprint` tag | ||
| 227 | |||
| 228 | 3. **Verify Device Attestation** (if present) | ||
| 229 | - Validate attestation token against platform-specific APIs | ||
| 230 | - Verify challenge nonce matches manifest `challengeNonce` | ||
| 231 | - Check attestation timestamp is recent (within 24 hours of recording) | ||
| 232 | |||
| 233 | 4. **Verify Frame Hashes** (advanced) | ||
| 234 | - Re-encode video to extract individual frames | ||
| 235 | - Generate SHA256 hashes of extracted frames | ||
| 236 | - Compare against hashes in manifest segments | ||
| 237 | - Verify frame count and timestamps match recording duration | ||
| 238 | |||
| 239 | 5. **Verify Recording Continuity** | ||
| 240 | - Check that segment timestamps are contiguous | ||
| 241 | - Verify pause durations match gaps between segments | ||
| 242 | - Validate total recording duration matches video length | ||
| 243 | |||
| 244 | 6. **Display Verification Badge** | ||
| 245 | - `verified_mobile` - Show "Verified" badge with hardware attestation icon | ||
| 246 | - `verified_web` - Show "Signed" badge | ||
| 247 | - `basic_proof` - Show "Basic Proof" indicator | ||
| 248 | - `unverified` - No badge or "Unverified" indicator | ||
| 249 | |||
| 250 | ## Example Event | ||
| 251 | |||
| 252 | ```json | ||
| 253 | { | ||
| 254 | "kind": 34236, | ||
| 255 | "pubkey": "...", | ||
| 256 | "created_at": 1730326800, | ||
| 257 | "tags": [ | ||
| 258 | ["d", "unique-video-identifier"], | ||
| 259 | ["title", "My Verified Video"], | ||
| 260 | ["url", "https://media.example.com/video.mp4", "720x1280"], | ||
| 261 | ["thumb", "https://media.example.com/thumb.jpg", "720x1280"], | ||
| 262 | ["duration", "6"], | ||
| 263 | ["verification", "verified_mobile"], | ||
| 264 | ["proofmode", "{\"sessionId\":\"session_1730326800000_1234\",\"challengeNonce\":\"a1b2c3d4e5f6789\",\"vineSessionStart\":\"2025-10-30T10:00:00.000Z\",\"vineSessionEnd\":\"2025-10-30T10:00:06.500Z\",\"totalDuration\":6500,\"recordingDuration\":6000,\"segments\":[{\"segmentId\":\"segment_1\",\"startTime\":\"2025-10-30T10:00:00.000Z\",\"endTime\":\"2025-10-30T10:00:06.000Z\",\"frameHashes\":[\"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\"]}],\"pauseProofs\":[],\"interactions\":[{\"timestamp\":\"2025-10-30T10:00:00.000Z\",\"interactionType\":\"start\",\"coordinates\":{\"x\":180,\"y\":640}}],\"finalVideoHash\":\"d4e5f6a7b8c9...\"}"], | ||
| 265 | ["device_attestation", "AAABBBCCC..."], | ||
| 266 | ["pgp_fingerprint", "1A2B3C4D5E6F7890..."] | ||
| 267 | ], | ||
| 268 | "content": "Check out this verified video!", | ||
| 269 | "sig": "..." | ||
| 270 | } | ||
| 271 | ``` | ||
| 272 | |||
| 273 | ## Security Considerations | ||
| 274 | |||
| 275 | ### Threat Model | ||
| 276 | |||
| 277 | ProofMode protects against: | ||
| 278 | - ✅ **Post-recording video editing** - Frame hashes detect any modifications | ||
| 279 | - ✅ **Deepfakes and synthetic videos** - Hardware attestation proves real device | ||
| 280 | - ✅ **Timestamp manipulation** - Device attestation includes trusted timestamps | ||
| 281 | - ✅ **Replay attacks** - Challenge nonce prevents reuse of attestations | ||
| 282 | |||
| 283 | ProofMode does NOT protect against: | ||
| 284 | - ❌ **Screen recording** - A user can screen-record another video | ||
| 285 | - ❌ **Camera lens manipulation** - Physical objects placed in front of camera | ||
| 286 | - ❌ **Compromised devices** - Rooted/jailbroken devices may forge attestations | ||
| 287 | - ❌ **Social engineering** - User can intentionally create misleading content | ||
| 288 | |||
| 289 | ### Privacy Considerations | ||
| 290 | |||
| 291 | - **Device Identifiers**: The `deviceId` field may be sensitive. Clients should: | ||
| 292 | - Hash or truncate device IDs before publishing | ||
| 293 | - Allow users to opt-out of device attestation | ||
| 294 | - Clearly indicate when ProofMode is active | ||
| 295 | |||
| 296 | - **Sensor Data**: Accelerometer and gyroscope data may reveal user location or behavior. Clients should: | ||
| 297 | - Allow disabling sensor data collection | ||
| 298 | - Sanitize or omit sensitive sensor readings | ||
| 299 | - Aggregate sensor data to reduce precision | ||
| 300 | |||
| 301 | - **PGP Keys**: Users should be able to: | ||
| 302 | - Rotate PGP keys periodically | ||
| 303 | - Revoke compromised keys | ||
| 304 | - Use separate keys for different purposes | ||
| 305 | |||
| 306 | ### Verification Best Practices | ||
| 307 | |||
| 308 | Verifying clients should: | ||
| 309 | |||
| 310 | 1. **Always check PGP signature** - This is the minimum verification | ||
| 311 | 2. **Validate device attestation** when present - But gracefully handle missing/invalid attestations | ||
| 312 | 3. **Display verification level prominently** - Users should understand confidence level | ||
| 313 | 4. **Cache verification results** - Re-verification is expensive | ||
| 314 | 5. **Handle expired attestations** - Attestations may expire after 24-48 hours | ||
| 315 | 6. **Warn on missing proofs** - But don't assume malice if ProofMode is absent | ||
| 316 | |||
| 317 | ## Reference Implementation | ||
| 318 | |||
| 319 | OpenVine provides a complete reference implementation: | ||
| 320 | - **Recording**: `ProofModeSessionService` in OpenVine mobile app | ||
| 321 | - **Publishing**: `VideoEventPublisher` adds ProofMode tags to Nostr events | ||
| 322 | - **Verification**: `ProofModeHelpers` and verification UI components | ||
| 323 | |||
| 324 | Source: https://github.com/openvine/openvine | ||
| 325 | |||
| 326 | ## Backwards Compatibility | ||
| 327 | |||
| 328 | This NIP is fully backwards compatible: | ||
| 329 | - Events without ProofMode tags are treated as unverified | ||
| 330 | - Older clients ignore ProofMode tags | ||
| 331 | - ProofMode is opt-in - videos without it still work normally | ||
| 332 | |||
| 333 | ## Future Extensions | ||
| 334 | |||
| 335 | Possible future enhancements: | ||
| 336 | |||
| 337 | 1. **Witness Signatures** - Multiple devices co-sign the same recording | ||
| 338 | 2. **Location Proofs** - GPS coordinates with cryptographic verification | ||
| 339 | 3. **Biometric Proof** - Prove human presence during recording | ||
| 340 | 4. **Chain of Custody** - Track video transfer and handling | ||
| 341 | 5. **Selective Disclosure** - Zero-knowledge proofs for privacy-preserving verification | ||
| 342 | |||
| 343 | ## References | ||
| 344 | |||
| 345 | - [NIP-01: Basic protocol flow description](https://github.com/nostr-protocol/nips/blob/master/01.md) | ||
| 346 | - [NIP-71: Video Events](https://github.com/nostr-protocol/nips/blob/master/71.md) | ||
| 347 | - [iOS App Attest](https://developer.apple.com/documentation/devicecheck/establishing_your_app_s_integrity) | ||
| 348 | - [Android Play Integrity](https://developer.android.com/google/play/integrity) | ||
| 349 | - [ProofMode Original Project](https://proofmode.org) | ||
| 350 | |||
| 351 | ## Authors | ||
| 352 | |||
| 353 | - Evan Henshaw-Plath (Rabble) - Divine.Video Project | ||
| 354 | |||
| 355 | ## License | ||
| 356 | |||
| 357 | This NIP is released into the public domain. | ||
| 358 | |||