mirror of
https://github.com/nostr-protocol/nips.git
synced 2025-12-11 17:48:50 +00:00
Compare commits
6 Commits
520b901629
...
proofmode-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
317e51b961 | ||
|
|
d44476eee8 | ||
|
|
62f0b14ae8 | ||
|
|
3ec830cd23 | ||
|
|
cc77619af8 | ||
|
|
8054526b87 |
36
17.md
36
17.md
@@ -6,9 +6,15 @@ Private Direct Messages
|
||||
|
||||
`draft` `optional`
|
||||
|
||||
This NIP defines an encrypted direct messaging scheme using [NIP-44](44.md) encryption and [NIP-59](59.md) seals and gift wraps.
|
||||
This NIP defines an encrypted chat scheme which uses [NIP-44](44.md) encryption and [NIP-59](59.md) seals and gift wraps.
|
||||
|
||||
## Direct Message Kind
|
||||
Any event sent to an encrypted chat MUST NOT be signed, and MUST be encrypted as described in [NIP-59](./59.md) and illustrated below. Omitting signatures makes messages deniable in case they are accidentally or maliciously leaked, while still allowing the recipient to authenticate them.
|
||||
|
||||
By convention, `kind 14` direct messages, `kind 15` file messages, and [`kind 7` reactions](./25.md) may be sent to an encrypted chat.
|
||||
|
||||
## Kind Definitions
|
||||
|
||||
### Chat Message
|
||||
|
||||
Kind `14` is a chat message. `p` tags identify one or more receivers of the message.
|
||||
|
||||
@@ -31,7 +37,7 @@ Kind `14` is a chat message. `p` tags identify one or more receivers of the mess
|
||||
|
||||
`.content` MUST be plain text. Fields `id` and `created_at` are required.
|
||||
|
||||
An `e` tag denotes the direct parent message this post is replying to.
|
||||
An `e` tag denotes the direct parent message this post is replying to.
|
||||
|
||||
`q` tags MAY be used when citing events in the `.content` with [NIP-21](21.md).
|
||||
|
||||
@@ -39,9 +45,7 @@ An `e` tag denotes the direct parent message this post is replying to.
|
||||
["q", "<event-id> or <event-address>", "<relay-url>", "<pubkey-if-a-regular-event>"]
|
||||
```
|
||||
|
||||
Kind `14`s MUST never be signed. If it is signed, the message might leak to relays and become **fully public**.
|
||||
|
||||
## File Message Kind
|
||||
## File Message
|
||||
|
||||
```jsonc
|
||||
{
|
||||
@@ -80,8 +84,6 @@ Kind `15` is used for sending encrypted file event messages:
|
||||
- `thumb` (optional) URL of thumbnail with same aspect ratio (encrypted with the same key, nonce)
|
||||
- `fallback` (optional) zero or more fallback file sources in case `url` fails (encrypted with the same key, nonce)
|
||||
|
||||
Just like kind `14`, kind `15`s MUST never be signed.
|
||||
|
||||
## Chat Rooms
|
||||
|
||||
The set of `pubkey` + `p` tags defines a chat room. If a new `p` tag is added or a current one is removed, a new room is created with a clean message history.
|
||||
@@ -92,7 +94,7 @@ An optional `subject` tag defines the current name/topic of the conversation. An
|
||||
|
||||
## Encrypting
|
||||
|
||||
Following [NIP-59](59.md), the **unsigned** `kind:14` & `kind:15` chat messages must be sealed (`kind:13`) and then gift-wrapped (`kind:1059`) to each receiver and the sender individually.
|
||||
Following [NIP-59](59.md), the **unsigned** chat messages must be sealed (`kind:13`) and then gift-wrapped (`kind:1059`) to each receiver and the sender individually.
|
||||
|
||||
```js
|
||||
{
|
||||
@@ -127,7 +129,7 @@ Clients SHOULD randomize `created_at` in up to two days in the past in both the
|
||||
|
||||
The gift wrap's `p` tag can be the receiver's main pubkey or an alias key created to receive DMs without exposing the receiver's identity.
|
||||
|
||||
Clients CAN offer disappearing messages by setting an `expiration` tag in the gift wrap of each receiver or by not generating a gift wrap to the sender's public key
|
||||
Clients MAY offer disappearing messages by setting an `expiration` tag in the gift wrap of each receiver or by not generating a gift wrap to the sender's public key. This tag SHOULD be included on the `kind 13` seal as well, in case it leaks.
|
||||
|
||||
## Publishing
|
||||
|
||||
@@ -145,15 +147,13 @@ Kind `10050` indicates the user's preferred relays to receive DMs. The event MUS
|
||||
}
|
||||
```
|
||||
|
||||
Clients SHOULD publish the gift-wrapped kind 1059 events that contain the sealed kind 14 (text) or kind 15 (file) rumors to the relays listed in the recipient’s kind 10050 event. If that is not found that indicates the user is not ready to receive messages under this NIP and clients shouldn't try.
|
||||
Clients SHOULD publish the gift-wrapped `kind 1059` events that contain the sealed rumors to the relays listed in the recipient’s kind 10050 event. If that is not found that indicates the user is not ready to receive messages under this NIP and clients shouldn't try.
|
||||
|
||||
## Relays
|
||||
|
||||
It's advisable that relays do not serve `kind:1059` to clients other than the ones tagged in them.
|
||||
Relays MAY protect message metadata by only serving `kind:1059` events to users p-tagged on the event (enforced using [NIP 42 AUTH](./42.md)).
|
||||
|
||||
It's advisable that users choose relays that conform to these practices.
|
||||
|
||||
Clients SHOULD guide users to keep `kind:10050` lists small (1-3 relays) and SHOULD spread it to as many relays as viable.
|
||||
Clients SHOULD guide users to keep `kind:10050` lists small (1-3 relays) and SHOULD spread them to as many relays as viable.
|
||||
|
||||
## Benefits & Limitations
|
||||
|
||||
@@ -170,12 +170,6 @@ This NIP offers the following privacy and security features:
|
||||
|
||||
The main limitation of this approach is having to send a separate encrypted event to each receiver. Group chats with more than 100 participants should find a more suitable messaging scheme.
|
||||
|
||||
## Implementation
|
||||
|
||||
Clients implementing this NIP should by default only connect to the set of relays found in their `kind:10050` list. From that they should be able to load all messages both sent and received as well as get new live updates, making it for a very simple and lightweight implementation that should be fast.
|
||||
|
||||
When sending a message to anyone, clients must then connect to the relays in the receiver's `kind:10050` and send the events there but can disconnect right after unless more messages are expected to be sent (e.g. the chat tab is still selected). Clients should also send a copy of their outgoing messages to their own `kind:10050` relay set.
|
||||
|
||||
## Examples
|
||||
|
||||
This example sends the message `Hola, que tal?` from `nsec1w8udu59ydjvedgs3yv5qccshcj8k05fh3l60k9x57asjrqdpa00qkmr89m` to `nsec12ywtkplvyq5t6twdqwwygavp5lm4fhuang89c943nf2z92eez43szvn4dt`.
|
||||
|
||||
146
43.md
Normal file
146
43.md
Normal file
@@ -0,0 +1,146 @@
|
||||
NIP-43
|
||||
======
|
||||
|
||||
Relay Access Metadata and Requests
|
||||
----------------------------------
|
||||
|
||||
`draft` `optional`
|
||||
|
||||
This NIP defines a way for relays to advertise membership lists, and for clients to request admission to relays on behalf of users.
|
||||
|
||||
## Membership Lists
|
||||
|
||||
Relays MAY publish a `kind 13534` event which indicates pubkeys that have access to a given relay. This event MUST be signed by the pubkey specified in the `self` field of the relay's [NIP 11](./11.md) document.
|
||||
|
||||
The following tags are required:
|
||||
|
||||
- A [NIP 70](./70.md) `-` tag
|
||||
- A `member` tag containing a hex pubkey should be included for each member
|
||||
|
||||
This list should not be considered exhaustive or authoritative. To determine membership, both a `kind 13534` event by the relay, and a `kind 10010` event by the member should be consulted.
|
||||
|
||||
Example:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"kind": 13534,
|
||||
"pubkey": "<nip11.self>",
|
||||
"tags": [
|
||||
["-"],
|
||||
["member", "c308e1f882c1f1dff2a43d4294239ddeec04e575f2d1aad1fa21ea7684e61fb5"],
|
||||
["member", "ee1d336e13779e4d4c527b988429d96de16088f958cbf6c074676ac9cfd9c958"]
|
||||
],
|
||||
// ...other fields
|
||||
}
|
||||
```
|
||||
|
||||
## Add User
|
||||
|
||||
Relays MAY publish a `kind 8000` event when a member is added to the relay. This event MUST be signed by the pubkey specified in the `self` field of the relay's [NIP 11](./11.md) document.
|
||||
|
||||
The following tags are required:
|
||||
|
||||
- A [NIP 70](./70.md) `-` tag
|
||||
- A `p` tag indicating the member's hex pubkey
|
||||
|
||||
Example:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"kind": 8000,
|
||||
"pubkey": "<nip11.self>",
|
||||
"tags": [
|
||||
["-"],
|
||||
["p", "c308e1f882c1f1dff2a43d4294239ddeec04e575f2d1aad1fa21ea7684e61fb5"]
|
||||
],
|
||||
// ...other fields
|
||||
}
|
||||
```
|
||||
|
||||
## Remove User
|
||||
|
||||
Relays MAY publish a `kind 8001` event when a member is removed from the relay. This event MUST be signed by the pubkey specified in the `self` field of the relay's [NIP 11](./11.md) document.
|
||||
|
||||
The following tags are required:
|
||||
|
||||
- A [NIP 70](./70.md) `-` tag
|
||||
- A `p` tag indicating the member's hex pubkey
|
||||
|
||||
Example:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"kind": 8001,
|
||||
"pubkey": "<nip11.self>",
|
||||
"tags": [
|
||||
["-"],
|
||||
["p", "c308e1f882c1f1dff2a43d4294239ddeec04e575f2d1aad1fa21ea7684e61fb5"]
|
||||
],
|
||||
// ...other fields
|
||||
}
|
||||
```
|
||||
|
||||
## Join Request
|
||||
|
||||
A user MAY send a `kind 28934` to a relay in order to request admission. It MUST have a `claim` tag containing an invite code. The event's `created_at` MUST be now, plus or minus a few minutes.
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"kind": 28934,
|
||||
"pubkey": "<user pubkey>",
|
||||
"tags": [
|
||||
["-"],
|
||||
["claim", "<invite code>"]
|
||||
],
|
||||
// ...other fields
|
||||
}
|
||||
```
|
||||
|
||||
Upon receiving a claim, a relay MUST notify the client as to what the status of the claim is using an `OK` message. Failed claims SHOULD use the same standard `"restricted: "` prefix specified by NIP 42.
|
||||
|
||||
Relays SHOULD update their `kind 13534` member list and MAY publish a `kind 8000` "add member" event.
|
||||
|
||||
Some examples:
|
||||
|
||||
```
|
||||
["OK", <event-id>, false, "restricted: that invite code is expired."]
|
||||
["OK", <event-id>, false, "restricted: that is an invalid invite code."]
|
||||
["OK", <event-id>, true, "duplicate: you are already a member of this relay."]
|
||||
["OK", <event-id>, true, "info: welcome to wss://relay.bunk.skunk!"]
|
||||
```
|
||||
|
||||
## Invite Request
|
||||
|
||||
Users may request a claim string from a relay by making a request for `kind 28935` events. This event MUST be signed by the pubkey specified in the `self` field of the relay's [NIP 11](./11.md) document.
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"kind": 28935,
|
||||
"pubkey": "<nip11.self>",
|
||||
"tags": [
|
||||
["-"],
|
||||
["claim", "<invite code>"],
|
||||
],
|
||||
// ...other fields
|
||||
}
|
||||
```
|
||||
|
||||
Note that these events are in the `ephemeral` range, which means relays must explicitly opt-in to this behavior by generating claims on the fly when requested. This allows relays to improve security by issuing a different claim for each request, only issuing claims to certain users, or expiring claims.
|
||||
|
||||
## Leave Request
|
||||
|
||||
A user MAY send a `kind 28936` to a relay in order to request that their access be revoked. The event's `created_at` MUST be now, plus or minus a few minutes. This event MUST include a [NIP 70](./70.md) `-` tag.
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"kind": 28936,
|
||||
"tags": [["-"]],
|
||||
// ...other fields
|
||||
}
|
||||
```
|
||||
|
||||
Relays SHOULD update their `kind 13534` member list and MAY publish a `kind 8001` "remove member" event.
|
||||
|
||||
## Implementation
|
||||
|
||||
Clients MUST only request `kind 28935` events from and send `kind 28934` events to relays which include this NIP in the `supported_nips` section of its [NIP 11](./11.md) relay information document.
|
||||
21
60.md
21
60.md
@@ -22,7 +22,7 @@ This NIP doesn't deal with users' *receiving* money from someone else, it's just
|
||||
3. A user has `kind:7376` events that represent the spending history of the wallet -- This history is for informational purposes only and is completely optional.
|
||||
|
||||
### Wallet Event
|
||||
```jsonc
|
||||
```javascript
|
||||
{
|
||||
"kind": 17375,
|
||||
"content": nip44_encrypt([
|
||||
@@ -45,11 +45,12 @@ Token events are used to record unspent proofs.
|
||||
|
||||
There can be multiple `kind:7375` events for the same mint, and multiple proofs inside each `kind:7375` event.
|
||||
|
||||
```jsonc
|
||||
```javascript
|
||||
{
|
||||
"kind": 7375,
|
||||
"content": nip44_encrypt({
|
||||
"mint": "https://stablenut.umint.cash",
|
||||
"unit": "sat",
|
||||
"proofs": [
|
||||
// one or more proofs in the default cashu format
|
||||
{
|
||||
@@ -69,6 +70,7 @@ There can be multiple `kind:7375` events for the same mint, and multiple proofs
|
||||
* `.content` is a [NIP-44](44.md) encrypted payload:
|
||||
* `mint`: The mint the proofs belong to.
|
||||
* `proofs`: unencoded proofs
|
||||
* `unit` the base unit the proofs are denominated in (eg: `sat`, `usd`, `eur`). Default: `sat` if omitted.
|
||||
* `del`: token-ids that were destroyed by the creation of this token. This assists with state transitions.
|
||||
|
||||
When one or more proofs of a token are spent, the token event should be [NIP-09](09.md)-deleted and, if some proofs are unspent from the same token event, a new token event should be created rolling over the unspent proofs and adding any change outputs to the new token event (the change output should include a `del` field).
|
||||
@@ -78,12 +80,13 @@ The `kind:5` _delete event_ created in the [NIP-09](09.md) process MUST have a t
|
||||
### Spending History Event
|
||||
Clients SHOULD publish `kind:7376` events to create a transaction history when their balance changes.
|
||||
|
||||
```jsonc
|
||||
```javascript
|
||||
{
|
||||
"kind": 7376,
|
||||
"content": nip44_encrypt([
|
||||
[ "direction", "in" ], // in = received, out = sent
|
||||
[ "amount", "1" ],
|
||||
[ "unit", "sat" ],
|
||||
[ "e", "<event-id-of-created-token>", "", "created" ]
|
||||
]),
|
||||
"tags": [
|
||||
@@ -93,6 +96,7 @@ Clients SHOULD publish `kind:7376` events to create a transaction history when t
|
||||
```
|
||||
|
||||
* `direction` - The direction of the transaction; `in` for received funds, `out` for sent funds.
|
||||
* `unit` the base unit of the amount (eg: `sat`, `usd`, `eur`). Default: `sat` if omitted.
|
||||
|
||||
Clients MUST add `e` tags to create references of destroyed and created token events along with the marker of the meaning of the tag:
|
||||
* `created` - A new token event was created.
|
||||
@@ -115,12 +119,13 @@ From those relays, the client should fetch wallet and token events.
|
||||
|
||||
### Spending token
|
||||
If Alice spends 4 sats from this token event
|
||||
```jsonc
|
||||
```javascript
|
||||
{
|
||||
"kind": 7375,
|
||||
"id": "event-id-1",
|
||||
"content": nip44_encrypt({
|
||||
"mint": "https://stablenut.umint.cash",
|
||||
"unit": "sat",
|
||||
"proofs": [
|
||||
{ "id": "1", "amount": 1 },
|
||||
{ "id": "2", "amount": 2 },
|
||||
@@ -134,12 +139,13 @@ If Alice spends 4 sats from this token event
|
||||
|
||||
Her client:
|
||||
* MUST roll over the unspent proofs:
|
||||
```jsonc
|
||||
```javascript
|
||||
{
|
||||
"kind": 7375,
|
||||
"id": "event-id-2",
|
||||
"content": nip44_encrypt({
|
||||
"mint": "https://stablenut.umint.cash",
|
||||
"unit": "sat",
|
||||
"proofs": [
|
||||
{ "id": "1", "amount": 1 },
|
||||
{ "id": "2", "amount": 2 },
|
||||
@@ -153,12 +159,13 @@ Her client:
|
||||
* MUST delete event `event-id-1`
|
||||
* SHOULD add the `event-id-1` to the `del` array of deleted token-ids.
|
||||
* SHOULD create a `kind:7376` event to record the spend
|
||||
```jsonc
|
||||
```javascript
|
||||
{
|
||||
"kind": 7376,
|
||||
"content": nip44_encrypt([
|
||||
[ "direction", "out" ],
|
||||
[ "amount", "4" ],
|
||||
[ "unit", "sat" ],
|
||||
[ "e", "<event-id-1>", "", "destroyed" ],
|
||||
[ "e", "<event-id-2>", "", "created" ],
|
||||
]),
|
||||
@@ -171,7 +178,7 @@ When creating a quote at a mint, an event can be used to keep the state of the q
|
||||
|
||||
However, application developers SHOULD use local state when possible and only publish this event when it makes sense in the context of their application.
|
||||
|
||||
```jsonc
|
||||
```javascript
|
||||
{
|
||||
"kind": 7374,
|
||||
"content": nip44_encrypt("quote-id"),
|
||||
|
||||
3
61.md
3
61.md
@@ -51,6 +51,7 @@ Clients MUST prefix the public key they P2PK-lock with `"02"` (for nostr<>cashu
|
||||
"pubkey": "<sender-pubkey>",
|
||||
"tags": [
|
||||
[ "proof", "{\"amount\":1,\"C\":\"02277c66191736eb72fce9d975d08e3191f8f96afb73ab1eec37e4465683066d3f\",\"id\":\"000a93d6f8a1d2c4\",\"secret\":\"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"b00bdd0467b0090a25bdf2d2f0d45ac4e355c482c1418350f273a04fedaaee83\\\",\\\"data\\\":\\\"02eaee8939e3565e48cc62967e2fde9d8e2a4b3ec0081f29eceff5c64ef10ac1ed\\\"}]\"}" ],
|
||||
[ "unit", "sat" ],
|
||||
[ "u", "https://stablenut.umint.cash" ],
|
||||
[ "e", "<nutzapped-event-id>", "<relay-hint>" ],
|
||||
[ "k", "<nutzapped-kind>"],
|
||||
@@ -62,6 +63,7 @@ Clients MUST prefix the public key they P2PK-lock with `"02"` (for nostr<>cashu
|
||||
* `.content` is an optional comment for the nutzap
|
||||
* `.tags`:
|
||||
* `proof` is one or more proofs P2PK-locked to the public key the recipient specified in their `kind:10019` event and including a DLEQ proof.
|
||||
* `unit` the base unit the proofs are denominated in (eg: `sat`, `usd`, `eur`). Default: `sat` if omitted.
|
||||
* `u` is the mint the URL of the mint EXACTLY as specified by the recipient's `kind:10019`.
|
||||
* `p` is the Nostr identity public key of nutzap recipient.
|
||||
* `e` is the event that is being nutzapped, if any.
|
||||
@@ -95,6 +97,7 @@ Multiple `kind:9321` events can be tagged in the same `kind:7376` event.
|
||||
"content": nip44_encrypt([
|
||||
[ "direction", "in" ], // in = received, out = sent
|
||||
[ "amount", "1" ],
|
||||
[ "unit", "sat" ],
|
||||
[ "e", "<7375-event-id>", "<relay-hint>", "created" ] // new token event that was created
|
||||
]),
|
||||
"tags": [
|
||||
|
||||
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