mirror of
https://github.com/nostr-protocol/nips.git
synced 2025-12-09 16:48:50 +00:00
Compare commits
2 Commits
hyperloglo
...
proofmode-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
317e51b961 | ||
|
|
d44476eee8 |
74
45.md
74
45.md
@@ -29,67 +29,15 @@ In case a relay uses probabilistic counts, it MAY indicate it in the response wi
|
||||
|
||||
Whenever the relay decides to refuse to fulfill the `COUNT` request, it MUST return a `CLOSED` message.
|
||||
|
||||
## HyperLogLog
|
||||
|
||||
Relays may return an HyperLogLog value together with the count, hex-encoded.
|
||||
|
||||
```
|
||||
["COUNT", <query_id>, {"count": <integer>, "hll": "<hex>"}]
|
||||
```
|
||||
|
||||
This is so it enables merging results from multiple relays and yielding a reasonable estimate of reaction counts, comment counts and follower counts, while saving many millions of bytes of bandwidth for everybody.
|
||||
|
||||
### Algorithm
|
||||
|
||||
This section describes the steps a relay should take in order to return HLL values to clients.
|
||||
|
||||
1. Upon receiving a filter, if it is eligible (see below) for HyperLogLog, compute the deterministic `offset` for that filter (see below);
|
||||
2. Initialize 256 registers to `0` for the HLL value;
|
||||
3. For all the events that are to be counted according to the filter, do this:
|
||||
1. Read the byte at position `offset` of the event `pubkey`, its value will be the register index `ri`;
|
||||
2. Count the number of leading zero bits starting at position `offset+1` of the event `pubkey` and add `1`;
|
||||
3. Compare that with the value stored at register `ri`, if the new number is bigger, store it.
|
||||
|
||||
That is all that has to be done on the relay side, and therefore the only part needed for interoperability.
|
||||
|
||||
On the client side, these HLL values received from different relays can be merged (by simply going through all the registers in HLL values from each relay and picking the highest value for each register, regardless of the relay).
|
||||
|
||||
And finally the absolute count can be estimated by running some methods I don't dare to describe here in English, it's better to check some implementation source code (also, there can be different ways of performing the estimation, with different quirks applied on top of the raw registers).
|
||||
|
||||
### Filter eligibility and `offset` computation
|
||||
|
||||
This NIP defines (for now) two filters eligible for HyperLogLog:
|
||||
|
||||
- `{"#e": ["<id>"], "kinds": [7]}`, i.e. a filter for `kind:7` events with a single `"e"` tag, which means the client is interested in knowing how many people have reacted to the target event `<id>`. In this case the `offset` will be given by reading the character at the position `32` of the hex `<id>` value as a base-16 number then adding `8` to it.
|
||||
- `{"#e": ["<id>"], "kinds": [6]}`, the same as above, but for `kind:6` reposts.
|
||||
- `{"#p": ["<pubkey>"], "kinds": [3]}`, i.e. a filter for `kind:3` events with a single `"p"` tag, which means the client is interested in knowing how many people "follow" the target `<pubkey>`. In this case the `offset` will be given by reading the character at the position `32` of the hex `<pubkey>` value as a base-16 number then adding `8` to it.
|
||||
- `{"#E": ["<id>"], "kinds": [1111]}`, i.e. a filter for the total number of comments any specific root event has received. In this case the `offset` will be given by reading the character at the position `32` of the hex `<id>` value as a base-16 number then adding `8` to it.
|
||||
|
||||
### Attack vectors
|
||||
|
||||
One could mine a pubkey with a certain number of zero bits in the exact place where the HLL algorithm described above would look for them in order to artificially make its reaction or follow "count more" than others. For this to work a different pubkey would have to be created for each different target (event id, followed profile etc). This approach is not very different than creating tons of new pubkeys and using them all to send likes or follow someone in order to inflate their number of followers. The solution is the same in both cases: clients should not fetch these reaction counts from open relays that accept everything, they should base their counts on relays that perform some form of filtering that makes it more likely that only real humans are able to publish there and not bots or artificially-generated pubkeys.
|
||||
|
||||
### `hll` encoding
|
||||
|
||||
The value `hll` value must be the concatenation of the 256 registers, each being a uint8 value (i.e. a byte). Therefore `hll` will be a 512-character hex string.
|
||||
|
||||
### Client-side usage
|
||||
|
||||
This algorithm also allows clients to combine HLL responses received from relays with HLL counts computed locally from raw events. It's recommended that clients keep track of HLL values locally and add to these on each message received from relays. For example:
|
||||
|
||||
- a client wants to keep track of the number of reactions an event Z has received over time;
|
||||
- the client has decided it will read reactions from relays A, B and C (the NIP-65 "read" relays of Z's author);
|
||||
- of these, only B and C support HLL responses, so the client fetches both and merges them locally;
|
||||
- then the client fetches all reaction events from A then manually applies each event to the HLL from the previous step, using the same algorithm described above;
|
||||
- finally, the client reads the estimate count from the HLL and displays that to the user;
|
||||
- optionally the client may store that HLL value (together with some "last-read-date" for relay A) and repeat the process again later:
|
||||
- this time it only needs to fetch the new reactions from A and add those to the HLL
|
||||
- and redownload the HLL values from B and C and just reapply them to the local value.
|
||||
|
||||
This procedure allows the client to download much less data.
|
||||
|
||||
## Examples
|
||||
|
||||
### Followers count
|
||||
|
||||
```
|
||||
["COUNT", <query_id>, {"kinds": [3], "#p": [<pubkey>]}]
|
||||
["COUNT", <query_id>, {"count": 238}]
|
||||
```
|
||||
|
||||
### Count posts and reactions
|
||||
|
||||
```
|
||||
@@ -97,7 +45,6 @@ This procedure allows the client to download much less data.
|
||||
["COUNT", <query_id>, {"count": 5}]
|
||||
```
|
||||
|
||||
|
||||
### Count posts approximately
|
||||
|
||||
```
|
||||
@@ -105,13 +52,6 @@ This procedure allows the client to download much less data.
|
||||
["COUNT", <query_id>, {"count": 93412452, "approximate": true}]
|
||||
```
|
||||
|
||||
### Followers count with HyperLogLog
|
||||
|
||||
```
|
||||
["COUNT", <subscription_id>, {"kinds": [3], "#p": [<pubkey>]}]
|
||||
["COUNT", <subscription_id>, {"count": 16578, "hll": "0607070505060806050508060707070706090d080b0605090607070b07090606060b0705070709050807080805080407060906080707080507070805060509040a0b06060704060405070706080607050907070b08060808080b080607090a06060805060604070908050607060805050d05060906090809080807050e0705070507060907060606070708080b0807070708080706060609080705060604060409070a0808050a0506050b0810060a0908070709080b0a07050806060508060607080606080707050806080c0a0707070a080808050608080f070506070706070a0908090c080708080806090508060606090906060d07050708080405070708"}]
|
||||
```
|
||||
|
||||
### Relay refuses to count
|
||||
|
||||
```
|
||||
|
||||
@@ -58,7 +58,6 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
|
||||
- [NIP-39: External Identities in Profiles](39.md)
|
||||
- [NIP-40: Expiration Timestamp](40.md)
|
||||
- [NIP-42: Authentication of clients to relays](42.md)
|
||||
- [NIP-43: Relay Access Metadata and Requests](43.md)
|
||||
- [NIP-44: Encrypted Payloads (Versioned)](44.md)
|
||||
- [NIP-45: Counting results](45.md)
|
||||
- [NIP-46: Nostr Remote Signing](46.md)
|
||||
@@ -183,8 +182,6 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
|
||||
| `7376` | Cashu Wallet History | [60](60.md) |
|
||||
| `7516` | Geocache log | [geocaching][geocaching] |
|
||||
| `7517` | Geocache proof of find | [geocaching][geocaching] |
|
||||
| `8000` | Add User | [43](43.md) |
|
||||
| `8001` | Remove User | [43](43.md) |
|
||||
| `9000`-`9030` | Group Control Events | [29](29.md) |
|
||||
| `9041` | Zap Goal | [75](75.md) |
|
||||
| `9321` | Nutzap | [61](61.md) |
|
||||
@@ -216,7 +213,6 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
|
||||
| `10377` | Proxy Announcement | [Nostr Epoxy][nostr-epoxy] |
|
||||
| `11111` | Transport Method Announcement | [Nostr Epoxy][nostr-epoxy] |
|
||||
| `13194` | Wallet Info | [47](47.md) |
|
||||
| `13534` | Membership Lists | [43](43.md) |
|
||||
| `17375` | Cashu Wallet Event | [60](60.md) |
|
||||
| `21000` | Lightning Pub RPC | [Lightning.Pub][lnpub] |
|
||||
| `22242` | Client Authentication | [42](42.md) |
|
||||
@@ -225,9 +221,6 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
|
||||
| `24133` | Nostr Connect | [46](46.md) |
|
||||
| `24242` | Blobs stored on mediaservers | [Blossom][blossom] |
|
||||
| `27235` | HTTP Auth | [98](98.md) |
|
||||
| `28934` | Join Request | [43](43.md) |
|
||||
| `28935` | Invite Request | [43](43.md) |
|
||||
| `28936` | Leave Request | [43](43.md) |
|
||||
| `30000` | Follow sets | [51](51.md) |
|
||||
| `30001` | Generic lists | 51 (deprecated) |
|
||||
| `30002` | Relay sets | [51](51.md) |
|
||||
|
||||
358
XX.md
Normal file
358
XX.md
Normal file
@@ -0,0 +1,358 @@
|
||||
# NIP-XX: ProofMode - Cryptographic Video Verification
|
||||
|
||||
`draft` `optional`
|
||||
|
||||
## Abstract
|
||||
|
||||
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.
|
||||
|
||||
## Motivation
|
||||
|
||||
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:
|
||||
|
||||
1. **Frame-level verification** - SHA256 hashes of captured frames prove recording continuity
|
||||
2. **Hardware attestation** - iOS App Attest and Android Play Integrity verify the recording device
|
||||
3. **Cryptographic signing** - PGP signatures ensure manifest authenticity
|
||||
4. **Tamper detection** - Any edit to the video invalidates the proof chain
|
||||
5. **Segment tracking** - Recording pauses are documented with sensor data
|
||||
|
||||
## Specification
|
||||
|
||||
### Event Tags
|
||||
|
||||
ProofMode data is attached to video events (typically Kind 34236) using the following tags:
|
||||
|
||||
#### Required Tags
|
||||
|
||||
- `["verification", "<level>"]` - Verification level (see Verification Levels below)
|
||||
- `["proofmode", "<manifest_json>"]` - Complete ProofManifest as compact JSON
|
||||
|
||||
#### Optional Tags
|
||||
|
||||
- `["device_attestation", "<token>"]` - Hardware attestation token from iOS App Attest or Android Play Integrity
|
||||
- `["pgp_fingerprint", "<fingerprint>"]` - PGP public key fingerprint used to sign the manifest
|
||||
|
||||
### Verification Levels
|
||||
|
||||
The `verification` tag indicates the strength of cryptographic proof:
|
||||
|
||||
- `verified_mobile` - Highest level: has device attestation + PGP signature + complete manifest
|
||||
- `verified_web` - Medium level: has PGP signature + complete manifest (no hardware attestation)
|
||||
- `basic_proof` - Low level: has proof data but no cryptographic signature
|
||||
- `unverified` - No meaningful proof data
|
||||
|
||||
### ProofManifest Structure
|
||||
|
||||
The `proofmode` tag contains a JSON object with the following structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"sessionId": "<unique_session_id>",
|
||||
"challengeNonce": "<16_char_nonce>",
|
||||
"vineSessionStart": "<ISO8601_timestamp>",
|
||||
"vineSessionEnd": "<ISO8601_timestamp>",
|
||||
"totalDuration": 6500,
|
||||
"recordingDuration": 6000,
|
||||
"segments": [
|
||||
{
|
||||
"segmentId": "<segment_id>",
|
||||
"startTime": "<ISO8601_timestamp>",
|
||||
"endTime": "<ISO8601_timestamp>",
|
||||
"duration": 3000,
|
||||
"frameHashes": [
|
||||
"<sha256_hash_1>",
|
||||
"<sha256_hash_2>",
|
||||
"..."
|
||||
],
|
||||
"frameTimestamps": [
|
||||
"<ISO8601_timestamp_1>",
|
||||
"<ISO8601_timestamp_2>",
|
||||
"..."
|
||||
],
|
||||
"sensorData": {
|
||||
"accelerometer": {"x": 0.1, "y": 0.2, "z": 9.8},
|
||||
"gyroscope": {"x": 0.01, "y": 0.02, "z": 0.01}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pauseProofs": [
|
||||
{
|
||||
"startTime": "<ISO8601_timestamp>",
|
||||
"endTime": "<ISO8601_timestamp>",
|
||||
"duration": 500,
|
||||
"sensorData": {
|
||||
"timestamp": "<ISO8601_timestamp>",
|
||||
"accelerometer": {"x": 0.1, "y": 0.2, "z": 9.8},
|
||||
"gyroscope": {"x": 0.01, "y": 0.02, "z": 0.01},
|
||||
"magnetometer": {"x": 45.0, "y": 12.0, "z": -30.0},
|
||||
"light": 150.0
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"timestamp": "<ISO8601_timestamp>",
|
||||
"interactionType": "touch",
|
||||
"coordinates": {"x": 180, "y": 640},
|
||||
"pressure": 0.5
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"interactions": [
|
||||
{
|
||||
"timestamp": "<ISO8601_timestamp>",
|
||||
"interactionType": "start|stop|touch",
|
||||
"coordinates": {"x": 180, "y": 640},
|
||||
"pressure": 0.5,
|
||||
"metadata": {}
|
||||
}
|
||||
],
|
||||
"finalVideoHash": "<sha256_hash_of_complete_video>",
|
||||
"deviceAttestation": {
|
||||
"token": "<platform_specific_attestation_token>",
|
||||
"platform": "iOS|Android|Web",
|
||||
"deviceId": "<device_identifier>",
|
||||
"isHardwareBacked": true,
|
||||
"createdAt": "<ISO8601_timestamp>",
|
||||
"challenge": "<challenge_nonce>",
|
||||
"metadata": {
|
||||
"attestationType": "app_attest|play_integrity|fallback",
|
||||
"deviceInfo": {
|
||||
"platform": "iOS",
|
||||
"model": "iPhone 15 Pro",
|
||||
"version": "17.0",
|
||||
"manufacturer": "Apple"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pgpSignature": {
|
||||
"signature": "-----BEGIN PGP SIGNATURE-----\n...\n-----END PGP SIGNATURE-----",
|
||||
"publicKey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n...\n-----END PGP PUBLIC KEY BLOCK-----",
|
||||
"publicKeyFingerprint": "1A2B3C4D5E6F7890..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Field Descriptions
|
||||
|
||||
#### Core Fields
|
||||
- `sessionId` - Unique identifier for the recording session
|
||||
- `challengeNonce` - Random nonce generated at session start, used in device attestation to prevent replay attacks
|
||||
- `vineSessionStart` / `vineSessionEnd` - Recording session boundaries
|
||||
- `totalDuration` - Total elapsed time in milliseconds (including pauses)
|
||||
- `recordingDuration` - Actual recording time in milliseconds (excluding pauses)
|
||||
|
||||
#### Segments
|
||||
Recording can be paused and resumed, creating multiple segments. Each segment contains:
|
||||
- `segmentId` - Unique segment identifier
|
||||
- `startTime` / `endTime` - Segment boundaries
|
||||
- `frameHashes` - Array of SHA256 hashes of captured video frames
|
||||
- `frameTimestamps` - Timestamps when each frame was captured (optional)
|
||||
- `sensorData` - Device sensor readings during recording (optional)
|
||||
|
||||
#### Pause Proofs
|
||||
When recording is paused, sensor data is collected to prove device continuity:
|
||||
- `startTime` / `endTime` - Pause boundaries
|
||||
- `sensorData` - Sensor readings during pause (accelerometer, gyroscope, magnetometer, light)
|
||||
- `interactions` - User touch/tap events during pause
|
||||
|
||||
#### Interactions
|
||||
User interactions recorded throughout the session:
|
||||
- `timestamp` - When interaction occurred
|
||||
- `interactionType` - Type of interaction (start, stop, touch)
|
||||
- `coordinates` - Screen coordinates of interaction
|
||||
- `pressure` - Touch pressure (optional)
|
||||
|
||||
#### Final Video Hash
|
||||
- `finalVideoHash` - SHA256 hash of the complete rendered video file
|
||||
|
||||
#### Device Attestation
|
||||
Platform-specific hardware attestation proving the device is genuine:
|
||||
- **iOS**: Uses App Attest API (iOS 14+)
|
||||
- **Android**: Uses Play Integrity API
|
||||
- **Web/Other**: Fallback software attestation
|
||||
|
||||
Fields:
|
||||
- `token` - Platform-specific attestation token
|
||||
- `platform` - Operating system (iOS, Android, Web)
|
||||
- `deviceId` - Device identifier
|
||||
- `isHardwareBacked` - Whether attestation uses hardware security module
|
||||
- `challenge` - Challenge nonce used in attestation (matches `challengeNonce`)
|
||||
- `metadata` - Platform-specific attestation details
|
||||
|
||||
#### PGP Signature
|
||||
Cryptographic signature of the entire manifest:
|
||||
- `signature` - PGP signature in ASCII-armored format
|
||||
- `publicKey` - PGP public key in ASCII-armored format
|
||||
- `publicKeyFingerprint` - Key fingerprint for quick lookup
|
||||
|
||||
## Implementation
|
||||
|
||||
### Recording Phase
|
||||
|
||||
1. **Start Session**
|
||||
- Generate unique `sessionId` and `challengeNonce`
|
||||
- Request hardware device attestation with challenge nonce
|
||||
- Initialize ProofMode session
|
||||
|
||||
2. **Capture Frames**
|
||||
- During recording, periodically capture video frames
|
||||
- Generate SHA256 hash of each frame
|
||||
- Store frame hashes with timestamps
|
||||
- Optionally collect sensor data (accelerometer, gyroscope, etc.)
|
||||
|
||||
3. **Handle Pauses**
|
||||
- When recording pauses, stop current segment
|
||||
- Begin collecting pause proof data (sensor readings, interactions)
|
||||
- When resuming, start new segment
|
||||
|
||||
4. **Finalize Session**
|
||||
- Stop recording and close final segment
|
||||
- Hash complete video file
|
||||
- Compile ProofManifest with all segments, pauses, and interactions
|
||||
- Sign manifest with PGP private key
|
||||
- Attach ProofManifest to video event as tags
|
||||
|
||||
### Verification Phase
|
||||
|
||||
To verify a ProofMode video, clients should:
|
||||
|
||||
1. **Extract ProofManifest**
|
||||
- Parse `proofmode` tag from video event
|
||||
- Extract `deviceAttestation` and `pgpSignature` from separate tags
|
||||
|
||||
2. **Verify PGP Signature**
|
||||
- Extract PGP public key from manifest
|
||||
- Verify signature of manifest JSON
|
||||
- Check public key fingerprint matches `pgp_fingerprint` tag
|
||||
|
||||
3. **Verify Device Attestation** (if present)
|
||||
- Validate attestation token against platform-specific APIs
|
||||
- Verify challenge nonce matches manifest `challengeNonce`
|
||||
- Check attestation timestamp is recent (within 24 hours of recording)
|
||||
|
||||
4. **Verify Frame Hashes** (advanced)
|
||||
- Re-encode video to extract individual frames
|
||||
- Generate SHA256 hashes of extracted frames
|
||||
- Compare against hashes in manifest segments
|
||||
- Verify frame count and timestamps match recording duration
|
||||
|
||||
5. **Verify Recording Continuity**
|
||||
- Check that segment timestamps are contiguous
|
||||
- Verify pause durations match gaps between segments
|
||||
- Validate total recording duration matches video length
|
||||
|
||||
6. **Display Verification Badge**
|
||||
- `verified_mobile` - Show "Verified" badge with hardware attestation icon
|
||||
- `verified_web` - Show "Signed" badge
|
||||
- `basic_proof` - Show "Basic Proof" indicator
|
||||
- `unverified` - No badge or "Unverified" indicator
|
||||
|
||||
## Example Event
|
||||
|
||||
```json
|
||||
{
|
||||
"kind": 34236,
|
||||
"pubkey": "...",
|
||||
"created_at": 1730326800,
|
||||
"tags": [
|
||||
["d", "unique-video-identifier"],
|
||||
["title", "My Verified Video"],
|
||||
["url", "https://media.example.com/video.mp4", "720x1280"],
|
||||
["thumb", "https://media.example.com/thumb.jpg", "720x1280"],
|
||||
["duration", "6"],
|
||||
["verification", "verified_mobile"],
|
||||
["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...\"}"],
|
||||
["device_attestation", "AAABBBCCC..."],
|
||||
["pgp_fingerprint", "1A2B3C4D5E6F7890..."]
|
||||
],
|
||||
"content": "Check out this verified video!",
|
||||
"sig": "..."
|
||||
}
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Threat Model
|
||||
|
||||
ProofMode protects against:
|
||||
- ✅ **Post-recording video editing** - Frame hashes detect any modifications
|
||||
- ✅ **Deepfakes and synthetic videos** - Hardware attestation proves real device
|
||||
- ✅ **Timestamp manipulation** - Device attestation includes trusted timestamps
|
||||
- ✅ **Replay attacks** - Challenge nonce prevents reuse of attestations
|
||||
|
||||
ProofMode does NOT protect against:
|
||||
- ❌ **Screen recording** - A user can screen-record another video
|
||||
- ❌ **Camera lens manipulation** - Physical objects placed in front of camera
|
||||
- ❌ **Compromised devices** - Rooted/jailbroken devices may forge attestations
|
||||
- ❌ **Social engineering** - User can intentionally create misleading content
|
||||
|
||||
### Privacy Considerations
|
||||
|
||||
- **Device Identifiers**: The `deviceId` field may be sensitive. Clients should:
|
||||
- Hash or truncate device IDs before publishing
|
||||
- Allow users to opt-out of device attestation
|
||||
- Clearly indicate when ProofMode is active
|
||||
|
||||
- **Sensor Data**: Accelerometer and gyroscope data may reveal user location or behavior. Clients should:
|
||||
- Allow disabling sensor data collection
|
||||
- Sanitize or omit sensitive sensor readings
|
||||
- Aggregate sensor data to reduce precision
|
||||
|
||||
- **PGP Keys**: Users should be able to:
|
||||
- Rotate PGP keys periodically
|
||||
- Revoke compromised keys
|
||||
- Use separate keys for different purposes
|
||||
|
||||
### Verification Best Practices
|
||||
|
||||
Verifying clients should:
|
||||
|
||||
1. **Always check PGP signature** - This is the minimum verification
|
||||
2. **Validate device attestation** when present - But gracefully handle missing/invalid attestations
|
||||
3. **Display verification level prominently** - Users should understand confidence level
|
||||
4. **Cache verification results** - Re-verification is expensive
|
||||
5. **Handle expired attestations** - Attestations may expire after 24-48 hours
|
||||
6. **Warn on missing proofs** - But don't assume malice if ProofMode is absent
|
||||
|
||||
## Reference Implementation
|
||||
|
||||
OpenVine provides a complete reference implementation:
|
||||
- **Recording**: `ProofModeSessionService` in OpenVine mobile app
|
||||
- **Publishing**: `VideoEventPublisher` adds ProofMode tags to Nostr events
|
||||
- **Verification**: `ProofModeHelpers` and verification UI components
|
||||
|
||||
Source: https://github.com/openvine/openvine
|
||||
|
||||
## Backwards Compatibility
|
||||
|
||||
This NIP is fully backwards compatible:
|
||||
- Events without ProofMode tags are treated as unverified
|
||||
- Older clients ignore ProofMode tags
|
||||
- ProofMode is opt-in - videos without it still work normally
|
||||
|
||||
## Future Extensions
|
||||
|
||||
Possible future enhancements:
|
||||
|
||||
1. **Witness Signatures** - Multiple devices co-sign the same recording
|
||||
2. **Location Proofs** - GPS coordinates with cryptographic verification
|
||||
3. **Biometric Proof** - Prove human presence during recording
|
||||
4. **Chain of Custody** - Track video transfer and handling
|
||||
5. **Selective Disclosure** - Zero-knowledge proofs for privacy-preserving verification
|
||||
|
||||
## References
|
||||
|
||||
- [NIP-01: Basic protocol flow description](https://github.com/nostr-protocol/nips/blob/master/01.md)
|
||||
- [NIP-71: Video Events](https://github.com/nostr-protocol/nips/blob/master/71.md)
|
||||
- [iOS App Attest](https://developer.apple.com/documentation/devicecheck/establishing_your_app_s_integrity)
|
||||
- [Android Play Integrity](https://developer.android.com/google/play/integrity)
|
||||
- [ProofMode Original Project](https://proofmode.org)
|
||||
|
||||
## Authors
|
||||
|
||||
- Evan Henshaw-Plath (Rabble)
|
||||
|
||||
## License
|
||||
|
||||
This NIP is released into the public domain.
|
||||
|
||||
Reference in New Issue
Block a user