diff --git a/10000-analytics-extension.md b/10000-analytics-extension.md new file mode 100644 index 0000000000..28b7d3164e --- /dev/null +++ b/10000-analytics-extension.md @@ -0,0 +1,195 @@ +# NIP-10000: UPlanet Analytics Events + +## Abstract + +This NIP defines a standardized event kind (`kind: 10000`) for sending analytics data as NOSTR events. This allows analytics to be stored on decentralized NOSTR relays instead of centralized servers, providing users with control over their data and enabling queryable, verifiable analytics. + +**Both encrypted and unencrypted analytics use the same kind 10000.** The encryption status is determined by the content field and tags. Encrypted analytics have encrypted content (NIP-44) and may include `["t", "encrypted"]` tag. + +## Motivation + +Traditional analytics systems rely on centralized servers that collect user data without user control. By using NOSTR events for analytics: + +- **Decentralized**: Data stored on user-controlled relays +- **Verifiable**: Cryptographically signed by the user +- **Queryable**: Can be queried via standard NOSTR filters +- **Privacy**: User chooses which relays store their analytics +- **Transparency**: All analytics events are publicly verifiable + +## Event Structure + +### Event Kind: 10000 + +Analytics events use `kind: 10000` and follow this structure: + +```json +{ + "kind": 10000, + "content": "{\"type\":\"page_view\",\"source\":\"email\",\"timestamp\":\"2024-01-01T12:00:00.000Z\",...}", + "tags": [ + ["t", "analytics"], + ["t", "page_view"], + ["source", "email"], + ["email", "user@example.com"], + ["url", "https://ipfs.domain.tld/page"], + ["referer", "https://referer.com"] + ], + "created_at": 1704110400, + "pubkey": "user_pubkey_hex", + "id": "event_id_hex", + "sig": "signature" +} +``` + +### Content Field + +The `content` field contains a JSON stringified object with analytics data: + +```json +{ + "type": "page_view|button_click|multipass_card_usage|...", + "source": "email|web|api|...", + "timestamp": "2024-01-01T12:00:00.000Z", + "email": "user@example.com", // optional + "current_url": "https://...", + "user_agent": "...", + "viewport": { + "width": 1920, + "height": 1080 + }, + "referer": "https://...", + "uspot_url": "https://u.domain.tld", + // ... custom fields +} +``` + +### Tags + +Required tags: +- `["t", "analytics"]` - Identifies this as an analytics event +- `["t", ""]` - The specific analytics event type (e.g., "page_view", "button_click") + +Optional tags: +- `["source", ""]` - Source of the analytics (e.g., "email", "web", "api") +- `["email", ""]` - User email (if available and user consents) +- `["url", ""]` - Current page URL +- `["referer", ""]` - Referer URL (if not "direct") +- `["uplanet", ""]` - UPlanet identifier +- Custom tags as needed + +## Usage + +### JavaScript Integration + +When `common.js` is loaded and NOSTR is connected, analytics can be sent as NOSTR events: + +```javascript +// Load common.js first (for NOSTR connection) + + + + +``` + +### Querying Analytics Events + +Analytics events can be queried using standard NOSTR filters: + +```javascript +// Query all analytics events from a user +{ + "kinds": [10000], + "authors": [""] +} + +// Query specific event type +{ + "kinds": [10000], + "#t": ["analytics", "page_view"], + "authors": [""] +} + +// Query by source +{ + "kinds": [10000], + "#source": ["email"], + "authors": [""] +} +``` + +## Privacy Considerations + +- Users control which relays store their analytics +- Events are cryptographically signed, ensuring authenticity +- Users can delete analytics events using NIP-09 (kind 5) +- Email addresses and other PII should only be included with user consent +- Consider using ephemeral events (NIP-40) for sensitive analytics + +## Implementation Notes + +- The `uPlanetAnalytics` system in `astro.js` automatically detects if NOSTR is available +- Falls back to HTTP POST to `/ping` endpoint if NOSTR is not connected +- Uses `publishNote()` from `common.js` to publish events +- Events are published silently (no user alerts) to avoid interrupting UX + +## Compatibility + +- Compatible with all NOSTR relays that support kind 10000 +- Works with standard NOSTR clients and filters +- Can be extended with additional tags as needed +- Integrates with NIP-101 (DID) for user identity + +## Examples + +### Page View Analytics + +```json +{ + "kind": 10000, + "content": "{\"type\":\"page_view\",\"source\":\"web\",\"timestamp\":\"2024-01-01T12:00:00.000Z\",\"current_url\":\"https://ipfs.domain.tld/page\",\"viewport\":{\"width\":1920,\"height\":1080}}", + "tags": [ + ["t", "analytics"], + ["t", "page_view"], + ["source", "web"], + ["url", "https://ipfs.domain.tld/page"] + ] +} +``` + +### MULTIPASS Card Usage Analytics + +```json +{ + "kind": 10000, + "content": "{\"type\":\"multipass_card_usage\",\"source\":\"email\",\"email\":\"user@example.com\",\"timestamp\":\"2024-01-01T12:00:00.000Z\"}", + "tags": [ + ["t", "analytics"], + ["t", "multipass_card_usage"], + ["source", "email"], + ["email", "user@example.com"] + ] +} +``` + +## References + +- [NIP-01](01.md): Basic protocol flow +- [NIP-09](09.md): Event Deletion +- [NIP-40](40.md): Expiration Timestamp +- [NIP-101](101.md): DID Document +- [UPLANET_EXTENSIONS.md](UPLANET_EXTENSIONS.md): UPlanet NIP Extensions Overview + + diff --git a/101-cookie-workflow-extension.md b/101-cookie-workflow-extension.md new file mode 100644 index 0000000000..d52653010b --- /dev/null +++ b/101-cookie-workflow-extension.md @@ -0,0 +1,458 @@ +# NIP-101 Cookie Workflow Extension + +UPlanet: Cookie-Based Workflow Automation +------------------------------------------ + +`draft` `optional` + +**Extends:** [NIP-101](101.md) (UPlanet: Decentralized Identity & Geographic Coordination) + +This NIP defines a workflow automation system that allows users to program their AI assistant using cookie-based data sources and visual workflow building (n8n-style interface). + +## Motivation + +Users upload cookies to enable authenticated web scraping (see [COOKIE_SYSTEM.md](../Astroport.ONE/IA/COOKIE_SYSTEM.md)). This extension allows users to: +- **Program workflows** visually (n8n-style interface) +- **Chain data sources** (cookie scrapers) → **processing** (AI, image recognition) → **output** → **actions** +- **Store workflows** as NOSTR events for portability and sharing +- **Execute workflows** automatically via `#cookie` tag in messages + +## Event Kinds + +### Workflow Definition (kind:31900) + +A **replaceable event** (kind 31900) stores a complete workflow definition. + +```jsonc +{ + "kind": 31900, + "tags": [ + ["d", "workflow_id"], // Unique workflow identifier + ["t", "cookie-workflow"], // Workflow type tag + ["t", "uplanet"], // UPlanet ecosystem tag + ["cookie", "youtube.com"], // Cookie domain used (optional) + ["cookie", "leboncoin.fr"] // Multiple cookie domains supported + ], + "content": "{WORKFLOW_JSON}" +} +``` + +**Workflow JSON Structure:** + +```jsonc +{ + "name": "YouTube to Blog Workflow", + "description": "Auto-generate blog posts from YouTube liked videos", + "version": "1.0.0", + "nodes": [ + { + "id": "source_1", + "type": "cookie_scraper", + "name": "YouTube Scraper", + "position": { "x": 100, "y": 100 }, + "parameters": { + "domain": "youtube.com", + "scraper": "youtube.com.sh", + "output": "liked_videos" + } + }, + { + "id": "filter_1", + "type": "filter", + "name": "Filter Recent", + "position": { "x": 300, "y": 100 }, + "parameters": { + "field": "published_at", + "operator": ">", + "value": "7 days ago" + }, + "connections": { + "input": ["source_1"] + } + }, + { + "id": "process_1", + "type": "ai_question", + "name": "Generate Summary", + "position": { "x": 500, "y": 100 }, + "parameters": { + "prompt": "Summarize this video: {video_title}", + "model": "gemma3:12b", + "slot": 0 + }, + "connections": { + "input": ["filter_1"] + } + }, + { + "id": "output_1", + "type": "nostr_publish", + "name": "Publish Article", + "position": { "x": 700, "y": 100 }, + "parameters": { + "kind": 30023, + "tags": ["t", "blog", "t", "youtube"] + }, + "connections": { + "input": ["process_1"] + } + } + ], + "triggers": [ + { + "type": "schedule", + "cron": "0 2 * * *" // Daily at 2 AM + }, + { + "type": "manual", + "tag": "#cookie" // Trigger via #cookie tag in message + } + ] +} +``` + +### Workflow Execution Request (kind:31901) + +A **regular event** (kind 31901) requests execution of a workflow. + +```jsonc +{ + "kind": 31901, + "tags": [ + ["e", ""], // Reference to kind 31900 workflow + ["t", "cookie-workflow-exec"], // Execution request tag + ["cookie", "youtube.com"] // Cookie domain to use + ], + "content": "{EXECUTION_PARAMETERS_JSON}" +} +``` + +**Execution Parameters:** + +```jsonc +{ + "workflow_id": "", + "trigger": "manual", // "manual" | "schedule" | "event" + "parameters": { + "override": { + "filter_1": { + "value": "1 day ago" // Override filter value + } + } + } +} +``` + +### Workflow Execution Result (kind:31902) + +A **regular event** (kind 31902) stores workflow execution results. + +```jsonc +{ + "kind": 31902, + "tags": [ + ["e", ""], // Reference to workflow definition + ["e", ""], // Reference to execution request + ["t", "cookie-workflow-result"], // Result tag + ["status", "success"] // "success" | "failed" | "partial" + ], + "content": "{EXECUTION_RESULT_JSON}" +} +``` + +## Node Types + +### Data Sources + +#### `cookie_scraper` +- **Purpose**: Execute domain-specific scraper using uploaded cookies +- **Parameters**: + - `domain`: Cookie domain (e.g., "youtube.com") + - `scraper`: Scraper script name (e.g., "youtube.com.sh") + - `output`: Output variable name +- **Output**: JSON array of scraped data + +#### `nostr_query` +- **Purpose**: Query NOSTR relay for events +- **Parameters**: + - `kind`: Event kind to query + - `author`: Author pubkey (optional) + - `tags`: Tag filters + - `limit`: Result limit +- **Output**: Array of NOSTR events + +### Processing Nodes + +#### `ai_question` +- **Purpose**: Ask AI question using Ollama +- **Parameters**: + - `prompt`: Question prompt (supports {variable} substitution) + - `model`: Ollama model name + - `slot`: Memory slot (0-12) +- **Output**: AI response text + +#### `image_recognition` +- **Purpose**: Recognize image using PlantNet +- **Parameters**: + - `image_url`: Image URL (from input or variable) + - `latitude`: GPS latitude + - `longitude`: GPS longitude +- **Output**: PlantNet recognition JSON + +#### `image_generation` +- **Purpose**: Generate image using ComfyUI +- **Parameters**: + - `prompt`: Image generation prompt + - `output_path`: uDRIVE output path +- **Output**: Generated image URL + +#### `filter` +- **Purpose**: Filter data based on conditions +- **Parameters**: + - `field`: Field to filter + - `operator`: ">", "<", "==", "!=", "contains", "regex" + - `value`: Filter value +- **Output**: Filtered data array + +#### `transform` +- **Purpose**: Transform data structure +- **Parameters**: + - `mapping`: Field mapping JSON + - `format`: Output format +- **Output**: Transformed data + +### Output Nodes + +#### `nostr_publish` +- **Purpose**: Publish NOSTR event +- **Parameters**: + - `kind`: Event kind + - `tags`: Tag array + - `content_template`: Content template with {variables} +- **Output**: Published event ID + +#### `udrive_save` +- **Purpose**: Save data to uDRIVE +- **Parameters**: + - `path`: uDRIVE path (e.g., "Documents/workflow_output.json") + - `format`: "json" | "text" | "csv" +- **Output**: Saved file path + +#### `email_send` +- **Purpose**: Send email notification +- **Parameters**: + - `to`: Recipient email + - `subject`: Email subject + - `template`: Email template +- **Output**: Email sent confirmation + +## Workflow Execution + +### Trigger Methods + +1. **Manual Trigger** (`#cookie` tag): + ``` + #BRO #cookie + ``` + - User sends message with `#cookie` tag and workflow identifier + - `1.sh` detects `#cookie` tag and passes to `UPlanet_IA_Responder.sh` + - `UPlanet_IA_Responder.sh` detects `#cookie` tag and calls `cookie_workflow_engine.sh` + - `cookie_workflow_engine.sh` loads workflow from NOSTR (kind 31900) + - Workflow nodes executed in sequence + - Execution result returned to user via NOSTR message + +2. **Scheduled Trigger**: + - `NOSTRCARD.refresh.sh` checks for scheduled workflows + - Executes workflows based on cron expressions + +3. **Event Trigger**: + - Workflow triggered by specific NOSTR events + - Example: New video liked → trigger blog generation workflow + +### Execution Flow + +``` +1. User creates workflow in n8n.html interface + ↓ +2. Workflow Definition (kind 31900) stored on NOSTR + ↓ +3. User sends message: #BRO #cookie + ↓ +4. 1.sh detects #cookie tag and passes to UPlanet_IA_Responder.sh + ↓ +5. UPlanet_IA_Responder.sh detects #cookie tag + ↓ +6. cookie_workflow_engine.sh loads workflow from NOSTR + ↓ +7. Workflow engine executes nodes in sequence: + - cookie_scraper → executes domain.sh script + - filter → filters data + - ai_question → processes with Ollama + - nostr_publish → publishes results + ↓ +8. Execution result returned to user via NOSTR message +``` + +## Integration with Cookie System + +Workflows can access cookie-based scrapers: + +```jsonc +{ + "id": "source_1", + "type": "cookie_scraper", + "parameters": { + "domain": "youtube.com", + "scraper": "youtube.com.sh", + "output": "videos" + } +} +``` + +The system automatically: +- Finds `.youtube.com.cookie` in user's MULTIPASS directory +- Executes `youtube.com.sh` with cookie file +- Returns scraped data to workflow + +## Example Workflows + +### 1. YouTube to Blog Auto-Post + +```jsonc +{ + "name": "YouTube Blog Generator", + "nodes": [ + { + "type": "cookie_scraper", + "parameters": { "domain": "youtube.com" } + }, + { + "type": "filter", + "parameters": { "field": "liked_at", "operator": ">", "value": "1 day ago" } + }, + { + "type": "ai_question", + "parameters": { "prompt": "Write a blog post about: {video_title}" } + }, + { + "type": "nostr_publish", + "parameters": { "kind": 30023, "tags": [["t", "blog"], ["t", "youtube"]] } + } + ] +} +``` + +### 2. Leboncoin Alert System + +```jsonc +{ + "name": "Leboncoin Price Alert", + "nodes": [ + { + "type": "cookie_scraper", + "parameters": { "domain": "leboncoin.fr" } + }, + { + "type": "filter", + "parameters": { "field": "price", "operator": "<", "value": 100 } + }, + { + "type": "email_send", + "parameters": { "to": "user@email.com", "subject": "New cheap item found!" } + } + ] +} +``` + +## Tags + +### Standard Tags + +- `d`: Workflow identifier (for kind 31900) +- `e`: Event reference (workflow ID, execution request ID) +- `t`: Type tags (`cookie-workflow`, `cookie-workflow-exec`, `cookie-workflow-result`) +- `cookie`: Cookie domain used (e.g., `["cookie", "youtube.com"]`) +- `status`: Execution status (`success`, `failed`, `partial`) + +### Custom Tags + +- `workflow_name`: Human-readable workflow name +- `workflow_version`: Version string +- `trigger_type`: `manual` | `schedule` | `event` + +## Client Behavior + +### Workflow Builder Interface + +Clients should provide: +- **Visual node editor** (drag-and-drop) +- **Node configuration forms** +- **Connection lines** between nodes +- **Workflow validation** +- **Save/load workflows** from NOSTR + +### Workflow Execution + +Clients should: +- **Monitor execution requests** (kind 31901) +- **Display execution progress** +- **Show execution results** (kind 31902) +- **Handle errors gracefully** + +## Relay Behavior + +Relays should: +- **Store workflow definitions** (kind 31900) as replaceable events +- **Store execution requests** (kind 31901) as regular events +- **Store execution results** (kind 31902) as regular events +- **Index by cookie domain** for efficient querying + +## Security Considerations + +- **Cookie access**: Only workflow owner can access their cookies +- **Workflow privacy**: Workflows stored on user's own relay +- **Execution validation**: Verify workflow ownership before execution +- **Rate limiting**: Prevent workflow execution abuse + +## Implementation + +### Frontend Interface +- **n8n.html**: Visual workflow builder interface + - Location: `UPassport/templates/n8n.html` + - Access: `http://localhost:54321/n8n` or `https://u.copylaradio.com/n8n` + - Features: + - Drag-and-drop node editor + - Node configuration forms + - Workflow save/load from NOSTR + - Visual connection lines + +### Backend Components +- **cookie_workflow_engine.sh**: Workflow execution engine + - Location: `Astroport.ONE/IA/cookie_workflow_engine.sh` + - Executes workflow nodes in sequence + - Handles cookie scrapers, AI processing, filters, and outputs + +- **UPlanet_IA_Responder.sh**: Main IA responder with #cookie tag support + - Detects `#cookie` tag in messages + - Calls `cookie_workflow_engine.sh` for execution + - Returns results to user + +### API Routes +- **GET /n8n**: Serves n8n.html workflow builder interface + - Location: `UPassport/54321.py` + +## Compatibility + +- **Requires**: NIP-42 (Authentication), NIP-101 (UPlanet Identity) +- **Uses**: Cookie System (see COOKIE_SYSTEM.md) +- **Integrates**: UPlanet_IA_Responder.sh for execution +- **Frontend**: Bootstrap 5, NostrTools.js +- **Backend**: Bash scripts, Python (question.py, nostr_send_note.py) + +--- + +**Version**: 1.0.0 +**Status**: Draft +**Author**: UPlanet/Astroport.ONE Team +**License**: AGPL-3.0 + diff --git a/101-n2-constellation-sync-extension.md b/101-n2-constellation-sync-extension.md new file mode 100644 index 0000000000..85858f6fdf --- /dev/null +++ b/101-n2-constellation-sync-extension.md @@ -0,0 +1,356 @@ +NIP-101 N² Constellation Synchronization Extension +=================================================== + +Astroport Relay Synchronization Protocol for UPlanet +----------------------------------------------------- + +`draft` `extension` `optional` + +This document describes the **N² (N-squared) constellation synchronization protocol** used by UPlanet/Astroport relays to maintain a distributed, resilient network of Nostr relays with automatic event synchronization. + +## Overview + +The N² protocol extends [NIP-101](101.md) with a **peer-to-peer relay synchronization mechanism** that creates a "constellation" of interconnected Astroport nodes. Each relay automatically syncs **21 specific event kinds** (including NIP-58 badges) across the network, ensuring data redundancy and censorship resistance. + +## Motivation + +Standard Nostr relay networks: +- ❌ Require manual relay configuration per client +- ❌ Have no automatic backup mechanism +- ❌ Lack geographic coordination +- ❌ Cannot guarantee data persistence + +The N² constellation protocol solves these problems by: +- ✅ Automatic peer discovery and synchronization +- ✅ Geographic hierarchical coordination (UMAP/SECTOR/REGION) +- ✅ Resilient data replication across nodes +- ✅ Zero-configuration for end users + +## Architecture + +### Hub-and-Satellite Model + +``` +Hub Central (1) +├── Coordinates 24 Satellites +├── Manages inter-satellite communication +├── Handles economic flows (Ẑen) +└── Provides global synchronization + +Satellites (24) +├── Local services (MULTIPASS, ZEN Cards) +├── Geographic UMAP management +├── Bidirectional sync with Hub +└── Peer sync with other Satellites +``` + +**Analogy:** Like a constellation of stars, where each node (relay) can see and communicate with multiple other nodes, creating redundancy and resilience. + +### N² Synchronization Matrix + +For a constellation of N relays: +- Each relay maintains connections to **N-1 peers** +- Total synchronization paths: **N × (N-1) = N²** +- Example: 25 relays (1 Hub + 24 Satellites) = **600 sync paths** + +## Synchronized Event Kinds + +The N² protocol synchronizes **18 event types** across all constellation members: + +### Core Events (NIP-01) +- **Kind 0** - Profile metadata +- **Kind 1** - Text notes +- **Kind 3** - Contact lists +- **Kind 5** - Deletion requests +- **Kind 6** - Reposts +- **Kind 7** - Reactions + +### Media Events (NIP-22) +- **Kind 21** - Media attachments +- **Kind 22** - Comments (NIP-22) + +### Long-Form Content (NIP-23) +- **Kind 30023** - Long-form articles +- **Kind 30024** - Draft articles + +### Identity & Credentials (NIP-101) +- **Kind 30800** - DID Documents (UPlanet Identity) +- **Kind 30500** - Permit Definitions (Oracle System) +- **Kind 30501** - Permit Requests +- **Kind 30502** - Permit Attestations +- **Kind 30503** - Permit Credentials + +### ORE Environmental System (NIP-101 ORE extension) +- **Kind 30312** - ORE Meeting Space (Persistent Geographic Space) +- **Kind 30313** - ORE Verification Meeting + +### Video Events (NIP-71 extension) +- **Kind 34235** - Video events with IPFS integration + +**Total:** 18 event kinds automatically synchronized across the constellation. + +## Synchronization Protocol + +### 1. Peer Discovery + +Each Astroport node maintains a constellation registry: + +```jsonc +{ + "constellation_id": "UPlanetV1", + "hub": { + "relay_url": "wss://relay.copylaradio.com", + "ipns": "k51qzi5uqu5dgy...", + "ipfsnodeid": "12D3KooWABC...", + "g1pub": "5fTwfbYUtCeoaFL...", + "role": "hub" + }, + "satellites": [ + { + "relay_url": "wss://satellite1.uplanet.org", + "ipns": "k51qzi5uqu5dhj...", + "ipfsnodeid": "12D3KooWDEF...", + "g1pub": "7gTwfbYUtCeoaFL...", + "role": "satellite", + "umap": "43.60,1.44" + } + ], + "sync_event_kinds": [0, 1, 3, 5, 6, 7, 8, 21, 22, 30008, 30009, 30023, 30024, 30312, 30313, 30500, 30501, 30502, 30503, 30800] +} +``` + +### 2. Automatic Synchronization Flow + +``` +1. EVENT PUBLISHED (Local Relay) + │ + ├─ Store locally (strfry DB) + │ + ├─ Check constellation registry + │ + ├─ FOR EACH peer IN constellation: + │ │ + │ ├─ IF event.kind IN sync_event_kinds: + │ │ │ + │ │ ├─ Send EVENT to peer relay + │ │ │ + │ │ └─ Log sync status (success/fail) + │ │ + │ └─ ELSE: Skip (not in sync list) + │ + └─ Update sync metrics +``` + +### 3. Bidirectional Sync + +Each relay maintains bidirectional connections: + +**Push (Outgoing):** +```bash +# Local event published +→ Push to all peers in constellation +→ Verify delivery (WebSocket ACK) +→ Retry on failure (exponential backoff) +``` + +**Pull (Incoming):** +```bash +# Periodic sync check (every 15 minutes) +→ Query each peer: "Give me events since " +→ Filter by sync_event_kinds +→ Store missing events locally +→ Update sync timestamp +``` + +### 4. Geographic Hierarchical Sync + +UPlanet uses **geographic coordination** to optimize sync: + +``` +REGION (1.0° x 1.0°) - ~10,000 km² +├── SECTOR (0.1° x 0.1°) - ~100 km² +│ ├── UMAP (0.01° x 0.01°) - ~1.2 km² +│ │ ├── Local Relay (Satellite) +│ │ └── Events tagged with ["g", "lat,lon"] +│ └── Sector Relay +└── Regional Hub +``` + +**Optimization:** Events tagged with geographic data are prioritized for sync to relays in the same SECTOR/REGION. + +## Configuration + +### Constellation Registry File + +Location: `~/.zen/tmp/${IPFSNODEID}/constellation.json` + +```jsonc +{ + "version": "1.0", + "constellation_id": "UPlanetV1", + "updated_at": "2025-11-03T12:00:00Z", + "peers": [ + { + "relay_url": "wss://relay.copylaradio.com", + "role": "hub", + "trusted": true, + "sync_enabled": true, + "last_sync": "2025-11-03T11:55:00Z" + } + ], + "sync_config": { + "event_kinds": [0, 1, 3, 5, 6, 7, 8, 21, 22, 30008, 30009, 30023, 30024, 30312, 30313, 30500, 30501, 30502, 30503, 30800], + "sync_interval": 900, + "retry_attempts": 3, + "retry_backoff": "exponential" + } +} +``` + +### Sync Script Integration + +**`UPLANET.refresh.sh`** (daily cron job): +```bash +# Sync constellation events +${MY_PATH}/CONSTELLATION.sync.sh + +# Sync UMAP data +${MY_PATH}/NOSTR.UMAP.refresh.sh + +# Sync Oracle data +${MY_PATH}/ORACLE.refresh.sh + +# Economic flows +${MY_PATH}/ZEN.ECONOMY.sh +``` + +## Use Cases + +### 1. User Publishes a Video + +``` +1. Alice uploads video to satellite1 (UMAP 43.60,1.44) +2. satellite1 publishes NIP-71 video event (kind 21 or 22) +3. N² sync triggers: + ├─ Hub receives event (immediate) + ├─ 23 other satellites receive event (15-min cycle) + └─ Event stored on 25 relays (100% redundancy) +4. Bob (on satellite15) can discover Alice's video +``` + +### 2. DID Document Update + +``` +1. Carol updates her DID (kind 30800) +2. Local relay stores update +3. N² sync propagates to all 24 peers +4. Carol's DID available on all constellation relays +5. Any service can resolve her identity from any relay +``` + +### 3. Permit Attestation (Multi-Relay Consensus) + +``` +1. Dave requests PERMIT_ORE_V1 (kind 30501) +2. Event synced to all constellation relays +3. 5 attesters (on different satellites) attest (kind 30502) +4. Attestations synced back to all relays +5. Oracle issues credential (kind 30503) +6. Credential available on all 25 relays +``` + +### 4. Geographic Content Discovery + +``` +1. User searches for content in UMAP 48.85,2.35 (Paris) +2. Query local relay: kind 1 WHERE ["g", "48.85,2.35"] +3. Local relay queries constellation peers in same SECTOR +4. Results aggregated from multiple sources +5. Geographic feed displays local + constellation content +``` + +## Performance Characteristics + +### Latency +- **Hub sync:** ~1-2 seconds (immediate push) +- **Satellite sync:** ~15 minutes (periodic pull) +- **Geographic priority:** ~5 minutes (same SECTOR) + +### Bandwidth +- **Per relay:** ~10-50 MB/day (18 event kinds) +- **Hub (25 relays):** ~250-1250 MB/day +- **Optimized:** Only syncs new events (incremental) + +### Storage +- **Constellation redundancy:** 25x storage per event +- **Benefit:** Zero data loss even if 24 relays fail +- **Trade-off:** Higher disk usage vs resilience + +## Security Considerations + +### Peer Verification +- Each peer verified by **G1 public key** +- Constellation membership requires: + - Valid `UPLANETNAME_G1` transaction (primo-transaction) + - IPFS node ID registration + - Trusted by Hub operator + +### Event Verification +- All events cryptographically signed (Schnorr) +- Signature verified before sync +- Invalid events rejected + +### Sybil Attack Prevention +- Constellation membership limited (Hub + 24 Satellites) +- Each satellite requires economic stake (machine capital) +- Ğ1 blockchain provides identity verification + +### Censorship Resistance +- 25 independent relay operators +- Event replication across all nodes +- No single point of control + +## Compatibility + +This extension is compatible with: +- ✅ [NIP-01](01.md) - Basic protocol flow +- ✅ [NIP-11](11.md) - Relay information document +- ✅ [NIP-42](42.md) - Authentication +- ✅ [NIP-101](101.md) - UPlanet Identity & Geographic Coordination + +Standard Nostr clients can use constellation relays as normal relays (no special protocol required). + +## Reference Implementation + +- **Sync Script:** `Astroport.ONE/RUNTIME/CONSTELLATION.sync.sh` +- **Constellation Registry:** `~/.zen/tmp/${IPFSNODEID}/constellation.json` +- **Hub Management:** `Astroport.ONE/RUNTIME/UPLANET.refresh.sh` +- **Repository:** [github.com/papiche/Astroport.ONE](https://github.com/papiche/Astroport.ONE) + +## Future Enhancements + +### Phase 1: Basic Sync (Current) +- ✅ 18 event kinds synchronized +- ✅ Hub-and-satellite topology +- ✅ Periodic pull-based sync + +### Phase 2: Real-Time Sync +- ⏳ WebSocket-based push (instant propagation) +- ⏳ Geographic proximity routing +- ⏳ Adaptive sync intervals based on activity + +### Phase 3: Mesh Network +- ⏳ Full mesh topology (N² connections) +- ⏳ Gossip protocol for event propagation +- ⏳ Dynamic peer discovery via DHT + +## License + +This specification is released under **AGPL-3.0**. + +## Authors + +- **papiche** - [github.com/papiche](https://github.com/papiche) +- **CopyLaRadio SCIC** - Cooperative implementation +- **Astroport.ONE community** - Constellation architecture + diff --git a/101.md b/101.md new file mode 100644 index 0000000000..283a978ad7 --- /dev/null +++ b/101.md @@ -0,0 +1,462 @@ +NIP-101 +======= + +UPlanet: Decentralized Identity & Geographic Coordination +---------------------------------------------------------- + +`draft` `optional` + +This NIP defines a comprehensive protocol for **decentralized identity management**, **geographic coordination**, and **verifiable credentials** on Nostr. It extends the Nostr protocol with four integrated systems: + +1. **Hierarchical GeoKeys** - Nostr keypairs derived from geographic coordinates +2. **Decentralized Identity (DID)** - W3C-compliant identities stored as Nostr events +3. **Oracle System** - Multi-signature permit management using Web of Trust +4. **ORE System** - Environmental obligations attached to geographic cells + +This NIP enables **geographically localized communication**, **self-sovereign identity**, **peer-validated credentials**, and **ecological commitment tracking** on a fully decentralized network. + +## Motivation + +Current Nostr implementations lack: +- **Geographic context** for location-based communication +- **Standardized identity documents** for self-sovereign identity +- **Verifiable credentials** for competence and authority +- **Environmental accountability** mechanisms + +UPlanet solves these problems by providing a unified protocol that: +- ✅ Creates **localized Nostr feeds** (UMAP, SECTOR, REGION) +- ✅ Implements **W3C-compliant DIDs** on Nostr (no centralized registries) +- ✅ Enables **peer-validated licensing** (driver's license, professional certifications) +- ✅ Tracks **environmental commitments** with economic incentives +- ✅ Supports **constellation synchronization** across multiple relays + +## Event Kinds + +### DID Documents (kind:30800) + +A special event with `kind:30800` "DID Document" stores W3C-compliant Decentralized Identifiers as Nostr addressable events. + +> **Note:** We use kind 30800 instead of 30311 to avoid conflict with [NIP-53](53.md) (Live Event), which officially uses kind 30311. + +```jsonc +{ + "kind": 30800, + "tags": [ + ["d", "did"], + ["t", "uplanet"], + ["t", "did-document"] + ], + "content": "{JSON_DID_DOCUMENT}" +} +``` + +DID Document Structure: + +```jsonc +{ + "@context": [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "did:nostr:", + "verificationMethod": [{ + "id": "did:nostr:#key-1", + "type": "Ed25519VerificationKey2020", + "controller": "did:nostr:", + "publicKeyMultibase": "z" + }], + "service": [ + { + "id": "#ipfs-drive", + "type": "IPFSDrive", + "serviceEndpoint": "ipns:////APP" + }, + { + "id": "#g1-wallet", + "type": "Ğ1Wallet", + "serviceEndpoint": "g1:" + } + ], + "verifiableCredential": [ + { + "@context": "https://www.w3.org/2018/credentials/v1", + "id": "urn:uuid:...", + "type": ["VerifiableCredential", "UPlanetLicense"], + "issuer": "did:nostr:", + "credentialSubject": { + "id": "did:nostr:", + "license": "PERMIT_ORE_V1" + } + } + ], + "metadata": { + "email": "user@example.com", + "contractStatus": "active", + "created": "2024-01-01T12:00:00Z", + "updated": "2025-10-30T14:30:00Z" + } +} +``` + +**DID Resolution:** +- **Format:** `did:nostr:` +- **Query:** Subscribe to `kind:30800` events where `pubkey == ` +- **Verification:** Use the embedded `verificationMethod` to verify signatures + +### Oracle System (kinds:30500-30503) + +The Oracle System enables **peer-validated certification** using the Web of Trust model. + +#### Permit Definition (kind:30500) + +Defines a license or permit type that can be requested by users and validated by peers. + +```jsonc +{ + "kind": 30500, + "pubkey": "", + "tags": [ + ["d", "PERMIT_ORE_V1"], + ["t", "uplanet"], + ["t", "permit-definition"] + ], + "content": "{ + \"id\": \"PERMIT_ORE_V1\", + \"name\": \"ORE Environmental Verifier\", + \"description\": \"Authority to verify ORE contracts\", + \"min_attestations\": 5, + \"validity_years\": 3, + \"reward_zen\": 10 + }" +} +``` + +#### Permit Request (kind:30501) + +A user requesting a permit submits their application with evidence. + +```jsonc +{ + "kind": 30501, + "pubkey": "", + "tags": [ + ["d", ""], + ["permit", "PERMIT_ORE_V1"], + ["t", "uplanet"] + ], + "content": "{ + \"statement\": \"I have expertise in ecological validation\", + \"evidence\": \"ipfs://Qm...\" + }" +} +``` + +#### Permit Attestation (kind:30502) + +Existing permit holders attest to the competence of applicants. + +```jsonc +{ + "kind": 30502, + "pubkey": "", + "tags": [ + ["d", ""], + ["e", ""], + ["p", ""], + ["permit", "PERMIT_ORE_V1"] + ], + "content": "{ + \"statement\": \"I attest to this applicant's competence\", + \"date\": \"2025-10-30T12:00:00Z\" + }" +} +``` + +#### Permit Credential (kind:30503) + +When sufficient attestations are collected, the Oracle issues a W3C Verifiable Credential. + +```jsonc +{ + "kind": 30503, + "pubkey": "", + "tags": [ + ["d", ""], + ["p", ""], + ["permit", "PERMIT_ORE_V1"] + ], + "content": "{ + \"@context\": \"https://www.w3.org/2018/credentials/v1\", + \"id\": \"urn:uuid:...\", + \"type\": [\"VerifiableCredential\", \"UPlanetLicense\"], + \"issuer\": \"did:nostr:\", + \"issuanceDate\": \"2025-10-30T12:00:00Z\", + \"expirationDate\": \"2028-10-30T12:00:00Z\", + \"credentialSubject\": { + \"id\": \"did:nostr:\", + \"license\": \"PERMIT_ORE_V1\", + \"attestations\": 5 + } + }" +} +``` + +**WoT Bootstrap ("Block 0"):** + +For a permit requiring **N signatures**, register **N+1 members** on the station. Each member attests all other members (except themselves), resulting in each member receiving **N attestations**. The Oracle then issues credentials to all members simultaneously. + +### ORE System (kinds:30312-30313) + +The ORE (Obligations Réelles Environnementales) System attaches **environmental obligations** to geographic cells (UMAP), creating a decentralized ecological registry. + +> **Note:** Kinds 30312 and 30313 are also used in [NIP-53](53.md) for Interactive Rooms and Conference Events. The ORE system extends NIP-53's meeting space concepts for environmental verification purposes, maintaining compatibility while adding geographic and ecological metadata. + +#### ORE Meeting Space (kind:30312) + +Defines a persistent geographic space for ORE verification meetings. + +```jsonc +{ + "kind": 30312, + "pubkey": "", + "tags": [ + ["d", "ore-space-43.60-1.44"], + ["g", "43.60,1.44"], + ["room", "UMAP_ORE_43.60_1.44"], + ["t", "uplanet"], + ["t", "ore-space"] + ], + "content": "{ + \"description\": \"Persistent geographic space for ORE verifications\", + \"vdo_url\": \"https://vdo.ninja/?room=UMAP_ORE_43.60_1.44\", + \"contractId\": \"ORE-2025-001\", + \"provider\": \"did:nostr:\" + }" +} +``` + +#### ORE Verification Meeting (kind:30313) + +Represents a scheduled or completed verification meeting for environmental compliance. + +```jsonc +{ + "kind": 30313, + "pubkey": "", + "tags": [ + ["d", "ore-verification-43.60-1.44-1730289600"], + ["a", "30312::ore-space-43.60-1.44"], + ["g", "43.60,1.44"], + ["start", "1730289600"], + ["permit", "PERMIT_ORE_V1"] + ], + "content": "{ + \"result\": \"compliant\", + \"evidence\": \"ipfs://Qm...\", + \"method\": \"satellite_imagery\", + \"notes\": \"Forest cover: 82%\" + }" +} +``` + +**Economic Flow:** +1. ORE Contract → UMAP DID (kind 30800) +2. ORE Meeting Space → NOSTR event (kind 30312) +3. Expert Validation → NOSTR event (kind 30313) +4. Automatic Payment → UPLANETNAME.RnD → UMAP Wallet +5. UMAP Redistribution → Local guardians/residents + +## Hierarchical GeoKeys + +Nostr keypairs are **deterministically derived** from geographic coordinates and a namespace string. + +**Seed Format:** +``` +"{UPLANETNAME}_{FORMATTED_LATITUDE}" "{UPLANETNAME}_{FORMATTED_LONGITUDE}" +``` + +Used as [libsodium](https://doc.libsodium.org/) salt & pepper for deterministic key generation. + +### Grid Levels + +| Level | Precision | Area Size | Example Seed | +|-------|-----------|-----------|--------------| +| **UMAP** | 0.01° | ~1.2 km² | `"UPlanetV148.85-2.34"` | +| **SECTOR** | 0.1° | ~100 km² | `"UPlanetV148.8-2.3"` | +| **REGION** | 1.0° | ~10,000 km² | `"UPlanetV148-2"` | + +Each level generates a Nostr keypair (secp256k1), IPFS key (ed25519), Ğ1 wallet (ed25519), and Bitcoin address (secp256k1) from the same seed, creating a **Twin-Key** mechanism. + +## Common Tags + +All UPlanet events SHOULD include these tags: + +### Geographic Tags +```json +["latitude", "FLOAT_STRING"] +["longitude", "FLOAT_STRING"] +["g", ""] +["application", "UPlanet"] +``` + +### Identity Tags +```json +["did", "did:nostr:"] +["t", "uplanet"] +``` + +### Permit Tags (30501-30503) +```json +["permit", "PERMIT_ID"] +["e", ""] +["p", ""] +``` + +### ORE Tags (30312-30313) +```json +["d", "ore-space-{lat}-{lon}"] +["g", "{lat},{lon}"] +["room", "UMAP_ORE_{lat}_{lon}"] +``` + +## Implementation + +### Authentication + +All UPlanet API operations require **[NIP-42](42.md) authentication**. + +### Key Management + +**Twin-Key Mechanism:** From a single seed, generate: +- NOSTR keypair (identity) +- IPFS key (storage) +- Ğ1 wallet (economy) +- Bitcoin address (interoperability) + +**Shamir Secret Sharing (SSSS):** Private keys are split into 3 fragments. Any 2 fragments can restore the full private key. + +### Constellation Synchronization + +UPlanet relays synchronize all NIP-101 events across the constellation network, including: +- Core events: 0, 1, 3, 5, 6, 7 +- Media: 21, 22 +- Files: 1063 (NIP-94 file metadata) +- Comments: 1111 (NIP-22 video comments) +- Content: 30023, 30024 +- Identity: 30800 +- Oracle: 30500-30503 +- ORE: 30312-30313 + +**Total:** 20 event types synchronized automatically. + +## Use Cases + +### 1. Localized Community Chat + +Alice posts from her neighborhood UMAP: + +```jsonc +{ + "kind": 1, + "pubkey": "", + "tags": [ + ["p", ""], + ["latitude", "43.6047"], + ["longitude", "1.4442"], + ["application", "UPlanet"] + ], + "content": "Community garden meeting tomorrow at 10am!" +} +``` + +Bob, subscribed to that UMAP's `npub`, sees the message instantly. + +### 2. ORE Verifier Certification + +Carol wants to become an ORE verifier: +1. Carol requests PERMIT_ORE_V1 +2. 5 experts attest Carol's competence +3. Oracle issues VC to Carol +4. VC added to Carol's DID +5. Carol receives 10 Ẑen reward +6. Carol can now validate ORE contracts + +### 3. Environmental Commitment Tracking + +Dave's UMAP commits to maintaining forest cover: +1. Create ORE Meeting Space (kind 30312) +2. Carol validates via satellite imagery +3. Carol publishes verification meeting (kind 30313) +4. System automatically triggers 10 Ẑen payment to Dave's UMAP +5. Dave redistributes to local guardians + +## Security Considerations + +### Location Disclosure +- **Risk:** Publishing precise coordinates reveals location +- **Mitigation:** Use broader grid levels (SECTOR, REGION) for privacy + +### Permit Fraud +- **Risk:** Fake attestations or credential forgery +- **Mitigation:** + - All events cryptographically signed (Schnorr) + - Verify attester holds valid permit before accepting attestation + - Multi-signature requirement (no single point of trust) + +### Sybil Attacks +- **Risk:** Single entity creates multiple fake identities to self-attest +- **Mitigation:** + - Require attesters to be in Ğ1 Web of Trust (verified humans) + - Economic cost (Ẑen) for permit applications + +## Compatibility + +- ✅ Uses standard event kinds where possible (0, 1, 3, etc.) +- ✅ Follows [NIP-01](01.md) (Basic protocol) +- ✅ Implements [NIP-10](10.md) (`e` and `p` tags) +- ✅ Implements [NIP-33](33.md) (Parameterized Replaceable Events) +- ✅ Implements [NIP-42](42.md) (Authentication) +- ✅ DIDs follow W3C DID Core Specification +- ✅ Verifiable Credentials follow W3C VC Data Model + +## References + +### Related NIPs +- [NIP-01](01.md): Basic protocol flow +- [NIP-10](10.md): Conventions for `e` and `p` tags +- [NIP-33](33.md): Parameterized Replaceable Events +- [NIP-42](42.md): Authentication +- [NIP-53](53.md): Live Activities (uses kind 30311) +- [NIP-99](99.md): Classified Listings (uses kind 30402) + +### W3C Standards +- [DID Core Specification](https://www.w3.org/TR/did-core/) +- [Verifiable Credentials Data Model](https://www.w3.org/TR/vc-data-model/) +- [Ed25519 Verification Key 2020](https://w3id.org/security/suites/ed25519-2020/v1) + +### UPlanet Documentation +- **Main Repository:** [github.com/papiche/Astroport.ONE](https://github.com/papiche/Astroport.ONE) +- **NIP-101 Repository:** [github.com/papiche/NIP-101](https://github.com/papiche/NIP-101) +- **Public Portal:** [UPlanet - L'Internet de Confiance](https://ipfs.copylaradio.com/ipns/copylaradio.com) + +## License + +This specification is released under **AGPL-3.0**. + +Implementation code is available at: +- [Astroport.ONE](https://github.com/papiche/Astroport.ONE) (AGPL-3.0) +- [NIP-101](https://github.com/papiche/NIP-101) (AGPL-3.0) +- [UPassport](https://github.com/papiche/UPassport) (AGPL-3.0) +- [UPlanet](https://github.com/papiche/UPlanet) (AGPL-3.0) + +## Authors + +**Lead Developers:** +- [papiche](https://github.com/papiche) - UPlanet & Astroport.ONE architecture +- CopyLaRadio SCIC - Cooperative governance and economic design + +**Contributors:** +- Astroport.ONE community +- Ğ1 libre currency community +- NOSTR protocol community + diff --git a/28-umap-extension.md b/28-umap-extension.md new file mode 100644 index 0000000000..5cb4c0ea00 --- /dev/null +++ b/28-umap-extension.md @@ -0,0 +1,399 @@ +# NIP-28 Extension: UMAP Geographic Channels + +`draft` `optional` + +This extension defines how to use NIP-28 Public Chat channels with **UMAP (Universal Map)** geographic cells in the UPlanet ecosystem, enabling location-based discussion rooms tied to decentralized identities. + +## Motivation + +Geographic chat rooms allow users to communicate within specific physical locations without requiring centralized infrastructure. By combining NIP-28 with UMAP DIDs (NIP-101), we create persistent, decentralized chat channels anchored to geographic coordinates. + +## Overview + +Each UMAP cell (0.01° × 0.01° geographic area, ~1.2 km²) can have its own: +- **Decentralized Identity (DID)** - Kind 30800 event (NIP-101) +- **Public Chat Channel** - Using NIP-28 (kind 42 messages) +- **Geographic Context** - Latitude/Longitude metadata + +The UMAP DID's `pubkey` or `event_id` serves as the **channel identifier** for NIP-28 messages. + +## Architecture + +``` +┌─────────────────────────────────────────────────────────┐ +│ UMAP Geographic Chat Flow │ +└─────────────────────────────────────────────────────────┘ + +1. UMAP DID (Kind 30800) is created for geographic cell + ├─ Pubkey: abc123... + ├─ Coordinates: { lat: 48.86, lon: 2.35 } + └─ Type: "UMAPGeographicCell" + +2. Channel ID is derived from UMAP DID + └─ channelId = npub1abc123... (or hex pubkey) + +3. Users send messages (Kind 42) referencing channel + ├─ Tag: ["e", , "", "root"] + └─ Tag: ["g", "48.86,2.35"] (optional geolocation) + +4. Clients subscribe to messages for specific UMAP + └─ Filter: { kinds: [42], "#e": [] } +``` + +## UMAP DID Discovery + +Before creating chat messages, clients must discover the UMAP DID for a geographic coordinate. + +### Finding UMAP DID (Kind 30800) + +```json +// REQ filter to find UMAP DID +{ + "kinds": [30800], + "#d": ["did"], + "#g": ["48.86,2.35"], + "limit": 1 +} +``` + +**Response:** +```json +{ + "id": "", + "pubkey": "abc123...", + "kind": 30800, + "created_at": 1730000000, + "tags": [ + ["d", "did"], + ["g", "48.86,2.35"] + ], + "content": "{\"@context\":[\"https://w3id.org/did/v1\"],\"id\":\"did:nostr:abc123...\",\"type\":\"UMAPGeographicCell\",\"geographicMetadata\":{\"coordinates\":{\"lat\":48.86,\"lon\":2.35}}}" +} +``` + +### Deriving Channel ID + +From the UMAP DID event: + +1. **Use `npub` encoding** (recommended): + ```javascript + channelId = NostrTools.nip19.npubEncode(umapDID.pubkey) + // Result: "npub1abc123..." + ``` + +2. **Or use hex pubkey directly**: + ```javascript + channelId = umapDID.pubkey + // Result: "abc123..." + ``` + +3. **Or use event ID** (for specific DID version): + ```javascript + channelId = umapDID.id + ``` + +**Best Practice**: Use `npub` encoding for human readability and compatibility with NIP-19 tools. + +## Sending UMAP Messages (Kind 42) + +### Root Message (Starting Conversation) + +```json +{ + "kind": 42, + "created_at": 1730000100, + "content": "Hello from Paris UMAP!", + "tags": [ + ["e", "npub1abc123...", "", "root"], + ["g", "48.86,2.35"] + ] +} +``` + +### Reply to Message + +```json +{ + "kind": 42, + "created_at": 1730000200, + "content": "Hi! I'm also in this UMAP", + "tags": [ + ["e", "npub1abc123...", "", "root"], + ["e", "", "", "reply"], + ["p", ""], + ["g", "48.86,2.35"] + ] +} +``` + +## Tag Specifications + +### Required Tags + +| Tag | Description | Example | +|-----|-------------|---------| +| `["e", , "", "root"]` | References UMAP DID as channel | `["e", "npub1abc...", "", "root"]` | + +### Optional Tags + +| Tag | Description | Example | +|-----|-------------|---------| +| `["g", ","]` | Geographic coordinates | `["g", "48.86,2.35"]` | +| `["e", , "", "reply"]` | Reply to specific message | `["e", "xyz789...", "", "reply"]` | +| `["p", ]` | Mention/reply to user | `["p", "def456..."]` | +| `["t", ]` | Topic hashtag | `["t", "paris"]` | + +## Client Implementation + +### 1. Initialize UMAP Chat + +```javascript +// 1. User provides coordinates (from GPS, profile, or manual input) +const currentLocation = { lat: 48.86, lon: 2.35 }; + +// 2. Search for UMAP DID +const umapKey = `${currentLocation.lat.toFixed(2)},${currentLocation.lon.toFixed(2)}`; + +const filter = { + kinds: [30800], + '#d': ['did'], + '#g': [umapKey], + limit: 1 +}; + +const sub = relay.sub([filter]); +let umapDID = null; + +sub.on('event', (event) => { + const content = JSON.parse(event.content); + if (content.type === 'UMAPGeographicCell') { + umapDID = event; + } +}); + +sub.on('eose', () => { + if (umapDID) { + // Found UMAP DID - use its npub as channel ID + channelId = NostrTools.nip19.npubEncode(umapDID.pubkey); + } else { + // No DID found - use fallback or create one + channelId = `UMAP_${umapKey}`; + } + sub.unsub(); + loadMessages(); +}); +``` + +### 2. Subscribe to Messages + +```javascript +// Subscribe to all messages in this UMAP channel +const messageSub = relay.sub([{ + kinds: [42], + "#e": [channelId], + limit: 50 +}]); + +messageSub.on('event', (event) => { + displayMessage(event); +}); +``` + +### 3. Send Message + +```javascript +async function sendMessage(content) { + const event = { + kind: 42, + created_at: Math.floor(Date.now() / 1000), + tags: [ + ["e", channelId, "", "root"], + ["g", `${currentLocation.lat.toFixed(2)},${currentLocation.lon.toFixed(2)}`] + ], + content: content + }; + + const signedEvent = await window.nostr.signEvent(event); + await relay.publish(signedEvent); +} +``` + +### 4. Change UMAP (Switch Room) + +```javascript +async function switchUMAP(newLat, newLon) { + // Unsubscribe from current channel + if (messageSub) messageSub.unsub(); + + // Update location + currentLocation = { lat: newLat, lon: newLon }; + + // Re-fetch UMAP DID for new location + await initUMAPChat(); + + // Subscribe to new channel + loadMessages(); +} +``` + +## Fallback Behavior + +If no UMAP DID (kind 30800) exists for a geographic cell: + +1. **Generic Channel ID**: Use `UMAP__` format + ```javascript + channelId = `UMAP_48.86_2.35`; + ``` + +2. **Display in UI**: Indicate no DID is registered yet + ``` + Channel ID: UMAP_48.86_2.35 (no DID yet) + ``` + +3. **Future Registration**: When a DID is created, clients can migrate to the new channel ID + +## Geographic Precision + +UMAP coordinates use **2 decimal places** (0.01° precision): +- Latitude: -90.00 to +90.00 +- Longitude: -180.00 to +180.00 +- Example: `48.86,2.35` (not `48.8566,2.3522`) + +This creates consistent ~1.2 km² cells worldwide. + +## UI Best Practices + +### Display Current UMAP + +``` +┌─────────────────────────────────────┐ +│ 🗨️ UMAP Chat Room │ +├─────────────────────────────────────┤ +│ 📍 UMAP: 48.86, 2.35 │ +│ 🔗 Channel: npub1abc...xyz │ +│ 👥 3 users │ +│ [Change Location] │ +├─────────────────────────────────────┤ +│ Messages appear here... │ +└─────────────────────────────────────┘ +``` + +### User Count (Optional) + +Estimate active users by counting unique `pubkey` values in recent messages: + +```javascript +const recentMessages = await fetchRecentMessages(channelId, 24 * 60 * 60); // Last 24h +const uniqueUsers = new Set(recentMessages.map(m => m.pubkey)); +displayUserCount(uniqueUsers.size); +``` + +## Security Considerations + +### 1. Geographic Privacy + +Users reveal their approximate location when joining UMAP channels. Clients SHOULD: +- Warn users about location sharing +- Allow anonymous participation (no profile linking) +- Support VPN/Tor for relay connections + +### 2. Spam Prevention + +Public geographic channels may attract spam. Clients MAY: +- Implement local muting (kind 44) +- Use proof-of-work for posting +- Require MULTIPASS registration (NIP-42 authentication) + +### 3. DID Ownership + +UMAP DIDs are not "owned" by a single entity. The DID creator's `pubkey` is the authoritative channel reference, but: +- Anyone can send messages (kind 42) to the channel +- No central moderator controls the channel +- Clients implement their own filtering/moderation + +## Integration with ORE System + +UMAP chats are part of the larger ORE (Obligations Réelles Environnementales) system: + +- **ORE Contracts** (Kind 30312): Reference UMAP DIDs for location-based compliance +- **ORE Verification** (Kind 30313): Discussion of environmental verification in UMAP chat +- **Flora Observations**: Plant identification events can be discussed in local UMAP + +See `ORE_SYSTEM.md` for full ecosystem documentation. + +## Example: Complete Chat Session + +```javascript +// === Client-side JavaScript Example === + +// 1. Connect to relay +const relay = await relayInit('wss://relay.example.com'); +await relay.connect(); + +// 2. Get user's location (example: Paris) +const location = { lat: 48.86, lon: 2.35 }; + +// 3. Find UMAP DID +const umapKey = "48.86,2.35"; +const umapDIDs = await relay.list([{ + kinds: [30800], + '#d': ['did'], + '#g': [umapKey] +}]); + +const umapDID = umapDIDs[0]; +const channelId = NostrTools.nip19.npubEncode(umapDID.pubkey); + +console.log('Connected to UMAP channel:', channelId); + +// 4. Subscribe to messages +const sub = relay.sub([{ + kinds: [42], + "#e": [channelId], + limit: 20 +}]); + +sub.on('event', (event) => { + console.log(`[${event.pubkey.slice(0, 8)}...]: ${event.content}`); +}); + +// 5. Send a message +const message = { + kind: 42, + created_at: Math.floor(Date.now() / 1000), + tags: [ + ["e", channelId, "", "root"], + ["g", "48.86,2.35"] + ], + content: "Bonjour from Paris UMAP! 🇫🇷" +}; + +const signedMessage = await window.nostr.signEvent(message); +await relay.publish(signedMessage); + +console.log('Message sent!'); +``` + +## Comparison with Standard NIP-28 + +| Feature | Standard NIP-28 | UMAP Extension | +|---------|----------------|----------------| +| Channel Creation | Kind 40 event required | Optional (uses existing UMAP DID) | +| Channel ID | Custom string/event ID | UMAP DID npub | +| Geographic Context | Not specified | Required (`g` tag) | +| Channel Discovery | Search by metadata | Search by coordinates | +| Persistence | Relay-dependent | DID-anchored (NIP-101) | +| Use Case | General chat | Location-based chat | + +## References + +- **NIP-28**: Public Chat - https://github.com/nostr-protocol/nips/blob/master/28.md +- **NIP-101**: UPlanet DID System - https://github.com/papiche/NIP-101 +- **NIP-10**: Event References - https://github.com/nostr-protocol/nips/blob/master/10.md +- **NIP-19**: bech32 Encoding - https://github.com/nostr-protocol/nips/blob/master/19.md +- **ORE System**: `ORE_SYSTEM.md` in Astroport.ONE documentation + +## License + +This extension is part of the UPlanet protocol and follows the Astroport.ONE project licensing. + diff --git a/42-oracle-permits-extension.md b/42-oracle-permits-extension.md new file mode 100644 index 0000000000..7d9d2fd80b --- /dev/null +++ b/42-oracle-permits-extension.md @@ -0,0 +1,379 @@ +NIP-42 Oracle Permits Extension +================================= + +Multi-Signature Permit Management via Web of Trust +--------------------------------------------------- + +`draft` `extension` `optional` + +This document describes an extension to [NIP-42](42.md) that enables **multi-signature permit management** for the UPlanet Oracle System. This extension implements the Web of Trust (WoT) model for decentralized competence certification. + +## Overview + +This extension adds support for: + +1. **Permit Definition Events (kind 30500)** - License types requiring N attestations +2. **Permit Request Events (kind 30501)** - Applications from users +3. **Permit Attestation Events (kind 30502)** - Expert signatures (multi-sig validation) +4. **Permit Credential Events (kind 30503)** - W3C Verifiable Credentials +5. **Authentication with Permit Credentials** - NIP-42 auth enhanced with permit verification + +## Motivation + +Standard NIP-42 authentication: +- ❌ Only validates user identity (signature verification) +- ❌ Cannot verify competencies or authority levels +- ❌ Lacks multi-signature validation mechanisms + +This extension solves these problems by: +- ✅ Adding competence-based authentication +- ✅ Enabling multi-signature permit validation +- ✅ Integrating W3C Verifiable Credentials into Nostr +- ✅ Supporting Web of Trust (WoT) attestation chains + +## Implementation + +### Permit System Event Kinds + +| Kind | Name | Description | Signed by | +|-------|------|-------------|-----------| +| 30500 | Permit Definition | License type definition | `UPLANETNAME.G1` (authority) | +| 30501 | Permit Request | Application from user | Applicant | +| 30502 | Permit Attestation | Expert signature | Attester (must have required permit) | +| 30503 | Permit Credential | W3C Verifiable Credential | `UPLANETNAME.G1` (authority) | + +### 1. Permit Definition (kind 30500) + +Defines a license type that users can request and experts can attest. + +```jsonc +{ + "kind": 30500, + "pubkey": "", + "tags": [ + ["d", "PERMIT_ORE_V1"], + ["t", "permit"], + ["t", "definition"], + ["t", "UPlanet"], + ["min_attestations", "5"], + ["required_license", ""], // Empty if no requirement + ["valid_duration_days", "1095"], // 3 years + ["revocable", "true"] + ], + "content": "{ + \"id\": \"PERMIT_ORE_V1\", + \"name\": \"ORE Environmental Verifier\", + \"description\": \"Authority to verify ORE environmental contracts\", + \"min_attestations\": 5, + \"valid_duration_days\": 1095, + \"reward_zen\": 10.0, + \"verification_method\": \"peer_attestation\" + }" +} +``` + +**Common permit types:** +- `PERMIT_ORE_V1` - Environmental verifier (5 attestations, 3 years) +- `PERMIT_DRIVER` - Driver's license WoT model (12 attestations, 15 years) +- `PERMIT_WOT_DRAGON` - UPlanet authority (3 attestations, unlimited) +- `PERMIT_MEDICAL_FIRST_AID` - First aid provider (8 attestations, 2 years) +- `PERMIT_BUILDING_ARTISAN` - Building artisan (10 attestations, 5 years) + +### 2. Permit Request (kind 30501) + +User requests a permit by publishing their application. + +```jsonc +{ + "kind": 30501, + "pubkey": "", + "tags": [ + ["d", ""], + ["l", "PERMIT_ORE_V1", "permit_type"], + ["p", ""], + ["t", "permit"], + ["t", "request"], + ["t", "UPlanet"] + ], + "content": "{ + \"request_id\": \"abc123...\", + \"permit_definition_id\": \"PERMIT_ORE_V1\", + \"applicant_did\": \"did:nostr:\", + \"statement\": \"I have expertise in ecological validation\", + \"evidence\": [\"ipfs://Qm...\"], + \"status\": \"pending\" + }" +} +``` + +### 3. Permit Attestation (kind 30502) + +Experts attest an applicant's competence (multi-signature validation). + +```jsonc +{ + "kind": 30502, + "pubkey": "", + "tags": [ + ["d", ""], + ["e", ""], + ["p", ""], + ["permit", "PERMIT_ORE_V1"], + ["attester_license", ""], + ["t", "permit"], + ["t", "attestation"], + ["t", "UPlanet"] + ], + "content": "{ + \"attestation_id\": \"att_xyz...\", + \"request_id\": \"abc123...\", + \"attester_did\": \"did:nostr:\", + \"statement\": \"I attest to this applicant's competence\", + \"signature\": \"\", + \"date\": \"2025-11-03T12:00:00Z\" + }" +} +``` + +**Attestation requirements:** +- Attester MUST hold the `required_license` permit (if specified) +- Each attester can only attest ONCE per request +- Attestation is cryptographically signed (Schnorr signature) + +### 4. Permit Credential (kind 30503) + +W3C Verifiable Credential issued after sufficient attestations. + +```jsonc +{ + "kind": 30503, + "pubkey": "", + "tags": [ + ["d", ""], + ["l", "PERMIT_ORE_V1", "permit_type"], + ["p", ""], + ["t", "permit"], + ["t", "credential"], + ["t", "verifiable-credential"], + ["t", "UPlanet"] + ], + "content": "{ + \"@context\": \"https://www.w3.org/2018/credentials/v1\", + \"id\": \"urn:uuid:\", + \"type\": [\"VerifiableCredential\", \"UPlanetLicense\"], + \"issuer\": \"did:nostr:\", + \"issuanceDate\": \"2025-11-03T12:00:00Z\", + \"expirationDate\": \"2028-11-03T12:00:00Z\", + \"credentialSubject\": { + \"id\": \"did:nostr:\", + \"license\": \"PERMIT_ORE_V1\", + \"licenseName\": \"ORE Environmental Verifier\", + \"attestationsCount\": 5 + }, + \"proof\": { + \"type\": \"Ed25519Signature2020\", + \"created\": \"2025-11-03T12:00:00Z\", + \"verificationMethod\": \"did:nostr:#uplanet-authority\", + \"proofValue\": \"\" + } + }" +} +``` + +### 5. Authentication with Permit Credentials + +Enhanced NIP-42 authentication that includes permit verification. + +```jsonc +{ + "kind": 22242, + "pubkey": "", + "tags": [ + ["relay", "wss://relay.copylaradio.com"], + ["challenge", ""], + ["did", "did:nostr:"], + ["permit", "PERMIT_ORE_V1"], + ["credential_id", ""], + ["t", "UPlanet"] + ], + "content": "", + "sig": "" +} +``` + +**Relay validation process:** +1. Verify standard NIP-42 authentication (signature + challenge) +2. If `permit` tag present, query kind 30503 events for this user +3. Verify credential is valid (not expired, not revoked) +4. Grant enhanced access based on permit level + +## Use Cases + +### 1. Driver's License (WoT Model) + +From the [CopyLaRadio article](https://www.copylaradio.com/blog/blog-1/post/reinventer-la-societe-avec-la-monnaie-libre-et-la-web-of-trust-148#): + +1. **Alice requests** `PERMIT_DRIVER` (kind 30501) +2. **12 certified drivers attest** Alice's competence (kind 30502) +3. **Oracle issues** W3C credential (kind 30503) +4. **The 12 attesters become** Alice's insurance mutual +5. **If Alice is dangerous**, attesters can revoke the permit + +### 2. ORE Environmental Verifier + +1. **Bob requests** `PERMIT_ORE_V1` (kind 30501) +2. **5 existing verifiers attest** Bob's competence (kind 30502) +3. **Oracle issues** W3C credential (kind 30503) +4. **Bob receives** 10 Ẑen reward from `UPLANETNAME_RND` +5. **Bob can now verify** ORE contracts for UMAPs + +### 3. WoT Dragon (UPlanet Authority) + +1. **Carol requests** `PERMIT_WOT_DRAGON` (kind 30501) +2. **3 community members attest** Carol's trustworthiness (kind 30502) +3. **Oracle issues** W3C credential (kind 30503) +4. **Carol receives** 100 Ẑen reward from `UPLANETNAME_G1` +5. **Carol gains** authority powers (infrastructure management, permit issuance, credential revocation) + +## Web of Trust Bootstrap ("Block 0") + +For NEW permits with no existing holders, the system uses **"Block 0" initialization**: + +**Principle:** For a permit requiring **N signatures**, minimum **N+1 members registered** on the station. + +Each member attests all other members (except themselves), giving exactly **N attestations** per member. + +**Example:** +- `PERMIT_ORE_V1` (5 signatures) → minimum **6 registered members** +- Each member receives 5 attestations (from the other 5) +- Oracle issues credentials to all 6 members simultaneously +- The initial group can now attest new applicants + +## Auto-Issuance Logic + +The Oracle System automatically issues credentials when thresholds are met: + +``` +IF (attestations_count >= min_attestations) AND (all_attesters_valid): + status = "validated" + TRIGGER credential_issuance(request_id) + UPDATE_DID(holder, credential) + IF (permit_has_reward): + TRANSFER_ZEN(UPLANETNAME_RND → holder, reward_amount) +``` + +## Security Considerations + +### Multi-Signature Validation +- Each permit requires **N attestations** from certified experts +- Attesters must hold the `required_license` permit (if specified) +- Creates **self-validating chain of trust** + +### Attestation Requirements +- One attestation per attester per request +- Attestations are cryptographically signed +- Signature chain is verifiable on-chain + +### Revocation +Permits can be revoked if: +- False attestations discovered +- Holder violates permit conditions +- Permit expires (validity period set) + +### Sybil Attack Prevention +- Require attesters in Ğ1 Web of Trust (verified humans) +- Economic cost (Ẑen) for permit applications +- Multiple attestations from independent sources + +## Integration with DID Documents + +When a permit is issued, it's automatically added to the holder's DID document (kind 30800): + +```jsonc +{ + "id": "did:nostr:", + "verifiableCredential": [ + { + "@context": "https://www.w3.org/2018/credentials/v1", + "type": ["VerifiableCredential", "UPlanetLicense"], + "credentialSubject": { + "license": "PERMIT_ORE_V1" + } + } + ] +} +``` + +## Economic Incentives + +Permit holders receive economic rewards: +- **WoT Dragon**: 100 Ẑen from `UPLANETNAME_G1` +- **ORE Verifier**: 10 Ẑen + payment per verification from `UPLANETNAME_RND` +- **Mediator**: Compensation for conflict resolution from `UPLANETNAME_RND` + +Rewards flow: +``` +UPLANETNAME_RND → Credential Holder + ↓ + Automatic on issuance (kind 30503) + ↓ + Blockchain transaction (Ğ1) + ↓ + DID update (metadata.permit_rewards) +``` + +## API Integration + +Relays can expose permit verification endpoints: + +**GET `/api/permit/verify//`** + +Response: +```jsonc +{ + "valid": true, + "permit_id": "PERMIT_ORE_V1", + "holder_hex": "", + "credential_id": "", + "issued_at": "2025-11-03T12:00:00Z", + "expires_at": "2028-11-03T12:00:00Z", + "attestations": 5, + "status": "active" +} +``` + +## Compatibility + +This extension is compatible with: +- ✅ [NIP-42](42.md) - Authentication to relays +- ✅ [NIP-01](01.md) - Basic protocol flow +- ✅ [NIP-33](33.md) - Parameterized Replaceable Events +- ✅ [NIP-101](101.md) - UPlanet Identity & DID system +- ✅ W3C Verifiable Credentials Data Model + +Standard NIP-42 relays can accept authentication and ignore permit tags. + +## Reference Implementation + +- **Backend:** `UPassport/oracle_system.py` (Python) +- **API:** `UPassport/54321.py` (FastAPI routes) +- **Frontend:** `UPassport/templates/oracle.html` (Web interface) +- **Scripts:** `Astroport.ONE/tools/oracle.WoT_PERMIT.init.sh` (Bootstrap "Block 0") +- **Repository:** [github.com/papiche/UPassport](https://github.com/papiche/UPassport) + +## Further Reading + +- [ORACLE_SYSTEM.md](../Astroport.ONE/docs/ORACLE_SYSTEM.md) - Complete documentation +- [CopyLaRadio Article](https://www.copylaradio.com/blog/blog-1/post/reinventer-la-societe-avec-la-monnaie-libre-et-la-web-of-trust-148#) - WoT philosophy +- [W3C Verifiable Credentials](https://www.w3.org/TR/vc-data-model/) - VC standard +- [NIP-101](101.md) - UPlanet Identity system + +## License + +This specification is released under **AGPL-3.0**. + +## Authors + +- **papiche** - [github.com/papiche](https://github.com/papiche) +- **CopyLaRadio SCIC** - Cooperative implementation + diff --git a/42-twin-key-extension.md b/42-twin-key-extension.md new file mode 100644 index 0000000000..0b1844c321 --- /dev/null +++ b/42-twin-key-extension.md @@ -0,0 +1,572 @@ +NIP-42 Twin-Key Extension +========================== + +Geographic Identity Authentication for UPlanet +---------------------------------------------- + +`draft` `extension` `optional` + +This document describes an extension to [NIP-42](42.md) that enables **Twin-Key authentication** for UPlanet's geographic identity system (see [NIP-101](101.md)). This extension is built on **Shamir Secret Sharing (SSSS)** and **deterministic key generation** to enable secure, location-based authentication without storing private keys. + +## Overview + +This extension adds support for: + +1. **SSSS-based authentication** - Users authenticate using 1/3 of their secret seed (NaCl) via QR code scanning +2. **Twin-Key mechanism** - Deterministic generation of multiple keypairs (Nostr, Ğ1, IPFS, Bitcoin, Monero) from a single seed +3. **Geographic key delegation** - Geographic keypairs (UMAP, SECTOR, REGION) derived from coordinates + namespace +4. **Key delegation contracts** - Delegation of authentication rights to UPlanet relay swarm network +5. **Smart contract integration** - Daily refresh scripts (`NOSTR.UMAP.refresh.sh`, `UPLANET.refresh.sh`) manage geographic key grids + +## Motivation + +Standard NIP-42 authentication: +- ❌ Requires full private key storage (security risk) +- ❌ No geographic context +- ❌ Cannot delegate authentication to relay networks +- ❌ Single keypair per identity + +This extension solves these problems by: +- ✅ **SSSS secret sharing** - Only 1/3 of secret needed for terminal authentication +- ✅ **Deterministic key generation** - All keypairs derived from single seed (SALT + PEPPER) +- ✅ **Geographic key delegation** - Location-based keys for UMAP/SECTOR/REGION grids +- ✅ **Relay swarm delegation** - Authentication contracts with IPFS swarm network +- ✅ **Multi-blockchain support** - Same seed generates keys for multiple blockchains + +## Core Concepts + +### 1. DISCO Seed (NaCl Secret) + +The foundation of UPlanet identity is a **DISCO seed** containing SALT and PEPPER: + +``` +DISCO = "/?${EMAIL}=${SALT}&nostr=${PEPPER}" +``` + +This seed is used as input to `keygen` tool for deterministic key generation across multiple blockchains. + +### 2. Shamir Secret Sharing (SSSS) + +The DISCO seed is split into **3 parts** using SSSS (2-of-3 threshold): + +``` +ssss-split -t 2 -n 3 +``` + +**Distribution:** +- **HEAD** → Encrypted with user's G1PUB (player's key) +- **MIDDLE** → Encrypted with CAPTAING1PUB (captain's key) +- **TAIL** → Encrypted with UPLANETG1PUB (UPlanet's key) + +**Security Model:** +- User needs **HEAD** (from QR code) + **TAIL** (from UPlanet network) to reconstruct DISCO +- Any 2 of 3 parts can restore the full secret +- No single party has full access to the secret + +### 3. MULTIPASS QR Code + +The QR code contains: + +``` +M-{SSSS_HEAD_B58}:{NOSTRNS} +``` + +Where: +- `SSSS_HEAD_B58` = Base58-encoded HEAD fragment +- `NOSTRNS` = IPNS key for user's storage vault + +**Terminal Authentication Flow:** +1. User scans QR code → extracts `SSSS_HEAD` and `NOSTRNS` +2. Terminal requests `SSSS_TAIL` from UPlanet network (encrypted with UPLANETG1PUB) +3. Terminal combines `SSSS_HEAD` + `SSSS_TAIL` → reconstructs DISCO +4. Terminal extracts SALT + PEPPER from DISCO +5. Terminal regenerates NSEC using `keygen -t nostr "${SALT}" "${PEPPER}"` +6. User is authenticated without storing full private key + +### 4. Twin-Key Deterministic Generation + +From a single seed (SALT + PEPPER), `keygen` generates multiple keypairs deterministically: + +| Key Type | Command | Use Case | +|----------|---------|----------| +| **Nostr** | `keygen -t nostr "${SALT}" "${PEPPER}"` | Identity & messaging | +| **Ğ1 (Duniter)** | `keygen -t duniter "${SALT}" "${PEPPER}"` | Economic transactions | +| **IPFS** | `keygen -t ipfs "${SALT}" "${PEPPER}"` | Storage & content | +| **Bitcoin** | `keygen -t bitcoin "${SALT}" "${PEPPER}"` | Interoperability | +| **Monero** | `keygen -t monero "${SALT}" "${PEPPER}"` | Privacy transactions | + +**Key Properties:** +- Same seed → same keys (deterministic) +- Different seed → different keys (unique per user) +- No key storage needed (regenerate on-demand from seed) + +### 5. Geographic Key Grids + +For geographic cells (UMAP, SECTOR, REGION), keys are derived from coordinates: + +**Seed Format:** +``` +"{UPLANETNAME}_{FORMATTED_LATITUDE}" "{UPLANETNAME}_{FORMATTED_LONGITUDE}" +``` + +**Grid Levels:** + +| Level | Precision | Area Size | Example Seed | +|-------|-----------|-----------|--------------| +| **UMAP** | 0.01° | ~1.2 km² | `"UPlanetV148.85" "UPlanetV1-2.34"` | +| **SECTOR** | 0.1° | ~100 km² | `"UPlanetV148.8" "UPlanetV1-2.3"` | +| **REGION** | 1.0° | ~10,000 km² | `"UPlanetV148" "UPlanetV1-2"` | + +**Smart Contract Management:** +- `NOSTR.UMAP.refresh.sh` - Daily refresh of UMAP keys and DID documents +- `UPLANET.refresh.sh` - Daily refresh of SECTOR/REGION keys and economic flows +- Geographic keys are ephemeral and regenerated daily from coordinates + +## Implementation + +### Authentication Flow + +#### Standard NIP-42 (for reference) + +``` +1. Client connects to relay +2. Relay sends: ["AUTH", ""] +3. Client signs event and sends: ["AUTH", ] +4. Relay validates and responds: ["OK", , true, ""] +``` + +#### Twin-Key Extension with SSSS + +``` +1. User scans MULTIPASS QR code on terminal (scan_new.html) +2. Terminal extracts SSSS_HEAD from QR code +3. Terminal requests SSSS_TAIL from UPlanet network +4. Terminal combines HEAD + TAIL → reconstructs DISCO +5. Terminal extracts SALT + PEPPER from DISCO +6. Terminal regenerates NSEC: keygen -t nostr "${SALT}" "${PEPPER}" +7. Terminal signs authentication event with regenerated NSEC +8. Terminal sends: ["AUTH", ] +9. Relay validates and responds: ["OK", , true, "authenticated:ssss"] +``` + +### Key Delegation Contract + +When authenticating, the user implicitly signs a **key delegation contract** with the UPlanet relay swarm: + +```jsonc +{ + "type": "key_delegation", + "user_hex": "", + "delegated_to": "", + "scope": "authentication", + "validity": "", + "signature": "" +} +``` + +**Contract Properties:** +- User delegates authentication rights to relay swarm +- Relay swarm can authenticate user on behalf of user +- Contract is signed with user's NSEC (regenerated from SSSS) +- Contract is stored in user's DID Document (kind 30800) + +### Personal Authentication Event (kind 22242) + +Standard NIP-42 event with UPlanet extensions: + +```jsonc +{ + "id": "", + "pubkey": "", + "kind": 22242, + "created_at": , + "tags": [ + ["relay", "wss://relay.copylaradio.com"], + ["challenge", ""], + ["did", "did:nostr:"], + ["umap", "43.60,1.44"], + ["delegation", ""], + ["application", "UPlanet"] + ], + "content": "", + "sig": "" +} +``` + +### UMAP Authentication Event (kind 22242) + +Secondary authentication with geographic keypair (derived from coordinates): + +```jsonc +{ + "id": "", + "pubkey": "", + "kind": 22242, + "created_at": , + "tags": [ + ["relay", "wss://relay.copylaradio.com"], + ["challenge", ""], + ["p", ""], + ["g", "spey6"], + ["latitude", "43.60"], + ["longitude", "1.44"], + ["grid_level", "UMAP"], + ["application", "UPlanet"] + ], + "content": "", + "sig": "" +} +``` + +**UMAP Key Derivation:** +```bash +# UMAP keypair derived from coordinates +UMAP_LAT="43.60" +UMAP_LON="1.44" +UMAP_SALT="${UPLANETNAME}_${UMAP_LAT}" +UMAP_PEPPER="${UPLANETNAME}_${UMAP_LON}" + +# Generate UMAP keys +UMAP_NSEC=$(keygen -t nostr "${UMAP_SALT}" "${UMAP_PEPPER}" -s) +UMAP_HEX=$(keygen -t nostr "${UMAP_SALT}" "${UMAP_PEPPER}" | nostr2hex) +``` + +### Relay Validation + +Relays implementing this extension MUST: + +1. **Validate SSSS reconstruction** (if applicable) + - Verify user provided valid SSSS_HEAD + - Verify UPlanet network provided valid SSSS_TAIL + - Verify reconstructed DISCO matches user's DID Document + +2. **Validate personal auth** (standard NIP-42) + - Verify signature + - Check timestamp (< 10 minutes old) + - Validate challenge matches + +3. **Validate UMAP auth** (if provided) + - Verify signature with UMAP pubkey (derived from coordinates) + - Check `p` tag references user's pubkey + - Verify geographic coordinates match UMAP derivation + - Optionally resolve DID Document to validate identity claims + +4. **Validate key delegation contract** + - Verify delegation contract signature + - Check contract validity period + - Store delegation in relay's authentication cache + +5. **Link identities** + - Store association: `user_hex ↔ UMAP_hex` + - Enable location-based queries + - Track user's current geographic context + +6. **Respond with enhanced status** + ```json + ["OK", "", true, "authenticated:twin-key:ssss", { + "user": "", + "umap": "", + "location": "43.60,1.44", + "grid_level": "UMAP", + "delegation": "" + }] + ``` + +### DID Resolution for Authentication + +Relays MAY resolve DID Documents to enhance authentication: + +``` +1. User authenticates with pubkey +2. Relay queries: kind 30800, pubkey == user_hex +3. Relay extracts from DID Document: + - verificationMethod (check key matches) + - service endpoints (IPFS drive, Ğ1 wallet) + - verifiableCredentials (UPlanet permits) + - keyDelegation (delegation contracts) +4. Relay grants enhanced permissions based on credentials +``` + +Example enhanced authentication response: + +```json +["OK", "", true, "authenticated:twin-key:ssss", { + "user": "", + "did": "did:nostr:", + "umap": "", + "credentials": ["PERMIT_ORE_V1"], + "services": ["IPFSDrive", "Ğ1Wallet"], + "delegation": "" +}] +``` + +## Use Cases + +### 1. Terminal Authentication (SSSS-based) + +Alice wants to authenticate on an Astroport terminal: + +``` +1. Alice scans MULTIPASS QR code on terminal (scan_new.html) +2. Terminal extracts SSSS_HEAD from QR code +3. Terminal requests SSSS_TAIL from UPlanet network +4. Terminal combines HEAD + TAIL → reconstructs DISCO +5. Terminal extracts SALT + PEPPER +6. Terminal regenerates NSEC: keygen -t nostr "${SALT}" "${PEPPER}" +7. Terminal signs authentication event +8. Alice is authenticated without storing full private key +``` + +### 2. Geographic Content Access + +Bob wants to post to his UMAP feed: + +``` +1. Bob authenticates with personal key (from SSSS) +2. Bob derives UMAP key from coordinates (43.60, 1.44) +3. Bob authenticates with UMAP key (Twin-Key) +4. Relay links Bob ↔ UMAP_43.60_1.44 +5. Bob publishes event with ["p", "UMAP_hex"] +6. Carol (subscribed to UMAP) sees Bob's post +``` + +### 3. Location-Based Permissions + +Dave is an ORE verifier and needs to validate a specific UMAP: + +``` +1. Dave authenticates with personal key (from SSSS) +2. Dave authenticates with TARGET_UMAP key (Twin-Key) +3. Relay resolves Dave's DID → finds PERMIT_ORE_V1 +4. Relay grants Dave write access to ORE events in this UMAP +5. Dave publishes kind 30313 (ORE verification) +``` + +### 4. Relay Swarm Delegation + +Eve delegates authentication to UPlanet relay swarm: + +``` +1. Eve authenticates using SSSS (HEAD + TAIL) +2. Eve signs key delegation contract +3. Contract stored in Eve's DID Document +4. Relay swarm can authenticate Eve on behalf of Eve +5. Eve can access services across all swarm relays +``` + +### 5. Multi-Location Authentication + +Frank travels between two UMAPs: + +``` +1. Frank authenticates at UMAP_A (Paris) using SSSS +2. Frank derives UMAP_A key from coordinates +3. Frank posts content → tagged with UMAP_A +4. Frank moves to UMAP_B (Toulouse) +5. Frank authenticates at UMAP_B using same SSSS +6. Frank derives UMAP_B key from new coordinates +7. Frank posts content → tagged with UMAP_B +8. Both feeds remain separate and geographically organized +``` + +### 6. Constellation Synchronization + +UPlanet relay constellation synchronizes authentication events: + +``` +1. User authenticates on relay.copylaradio.com using SSSS +2. Authentication event (kind 22242) is published +3. Constellation members sync this event +4. User is now authenticated on all constellation relays +5. Seamless roaming between UPlanet nodes +``` + +## Client Behavior + +### Authenticating with SSSS + Twin-Key + +Clients SHOULD: + +1. **Scan MULTIPASS QR code** + - Extract `SSSS_HEAD` and `NOSTRNS` from QR code + - Format: `M-{SSSS_HEAD_B58}:{NOSTRNS}` + +2. **Request SSSS_TAIL from UPlanet network** + - Query UPlanet network for encrypted `SSSS_TAIL` + - Decrypt using UPLANETG1PUB (public key) + +3. **Reconstruct DISCO seed** + - Combine `SSSS_HEAD` + `SSSS_TAIL` using `ssss-combine` + - Extract SALT + PEPPER from DISCO + +4. **Regenerate keypairs** + - Nostr: `keygen -t nostr "${SALT}" "${PEPPER}"` + - Ğ1: `keygen -t duniter "${SALT}" "${PEPPER}"` + - IPFS: `keygen -t ipfs "${SALT}" "${PEPPER}"` + +5. **Derive geographic keys** (if needed) + - UMAP: `keygen -t nostr "${UPLANETNAME}_${LAT}" "${UPLANETNAME}_${LON}"` + - SECTOR: `keygen -t nostr "${UPLANETNAME}_${LAT_ROUNDED}" "${UPLANETNAME}_${LON_ROUNDED}"` + +6. **Sign authentication events** + - Personal auth: signed with regenerated NSEC + - UMAP auth: signed with derived UMAP key + +7. **Send both to relay** + - Send personal authentication event + - Send UMAP authentication event (if applicable) + +8. **Store delegation contract** + - Sign key delegation contract + - Store in DID Document (kind 30800) + +Example JavaScript implementation: + +```javascript +async function authenticateWithSSSS(qrCode, relay, latitude, longitude) { + // 1. Decode QR code + const decoded = base58Decode(qrCode.substring(2)); // Skip "M-" + const [ssssHead, nostrns] = decoded.split(':'); + + // 2. Request SSSS_TAIL from UPlanet network + const ssssTailEncrypted = await fetchUPlanetSSSSTail(nostrns); + const ssssTail = await decryptWithUPlanetKey(ssssTailEncrypted); + + // 3. Reconstruct DISCO + const disco = await ssssCombine(ssssHead, ssssTail); + const [salt, pepper] = extractSaltPepper(disco); + + // 4. Regenerate Nostr keypair + const nsec = await keygen('nostr', salt, pepper, { secret: true }); + const userKeys = { privateKey: nsec, publicKey: getPublicKey(nsec) }; + + // 5. Derive UMAP keypair + const umapSalt = `${UPLANETNAME}_${latitude.toFixed(2)}`; + const umapPepper = `${UPLANETNAME}_${longitude.toFixed(2)}`; + const umapNsec = await keygen('nostr', umapSalt, umapPepper, { secret: true }); + const umapKeys = { privateKey: umapNsec, publicKey: getPublicKey(umapNsec) }; + + // 6. Get challenge from relay + const challenge = await relay.getChallenge(); + + // 7. Sign personal authentication + const personalAuth = await signEvent({ + kind: 22242, + tags: [ + ["relay", relay.url], + ["challenge", challenge], + ["did", `did:nostr:${userKeys.publicKey}`], + ["umap", `${latitude.toFixed(2)},${longitude.toFixed(2)}`], + ["application", "UPlanet"] + ], + content: "" + }, userKeys.privateKey); + + // 8. Sign UMAP authentication + const umapAuth = await signEvent({ + kind: 22242, + tags: [ + ["relay", relay.url], + ["challenge", challenge], + ["p", userKeys.publicKey], + ["g", geohash(latitude, longitude)], + ["latitude", latitude.toFixed(2)], + ["longitude", longitude.toFixed(2)], + ["grid_level", "UMAP"], + ["application", "UPlanet"] + ], + content: "" + }, umapKeys.privateKey); + + // 9. Send both + await relay.send(["AUTH", personalAuth]); + await relay.send(["AUTH", umapAuth]); + + // 10. Sign and store delegation contract + const delegationContract = await signDelegationContract(userKeys.privateKey, relay.swarmId); + await updateDIDDocument(userKeys.publicKey, { keyDelegation: delegationContract }); +} +``` + +### Caching Authentication + +Clients SHOULD cache authentication state: +- Store `user_hex ↔ UMAP_hex` mapping in localStorage +- Store `SSSS_HEAD` in secure storage (encrypted) +- Check if current location matches cached UMAP +- Re-authenticate only when location changes significantly (> 0.01°) +- **Never store full NSEC** - always regenerate from SSSS + +## Privacy Considerations + +- **Location disclosure:** UMAP authentication reveals user's approximate location (1.2 km² grid) +- **Mitigation:** Users can authenticate at broader grid levels (SECTOR: 100 km², REGION: 10,000 km²) +- **SSSS security:** Only 1/3 of secret needed for authentication (reduces attack surface) +- **Ephemeral keys:** UMAP keypairs should be ephemeral and derived on-demand +- **DID privacy:** DID Documents may contain sensitive information (use selective disclosure) +- **No key storage:** Keys are regenerated on-demand from SSSS (no persistent storage) + +## Security Considerations + +- **SSSS threshold:** 2-of-3 parts required (prevents single point of failure) +- **Key derivation:** All keys deterministically derived from seed (no randomness) +- **Replay attacks:** Authentication events have short TTL (< 10 minutes) +- **Sybil attacks:** Ğ1 Web of Trust integration prevents fake geographic identities +- **Signature verification:** Both user and UMAP signatures must be validated independently +- **Delegation contracts:** Must be signed and stored in DID Documents +- **Smart contract refresh:** Daily refresh scripts ensure geographic keys are up-to-date + +## Smart Contract Integration + +### Daily Refresh Scripts + +**`NOSTR.UMAP.refresh.sh`** (daily cron): +- Refreshes UMAP keys from coordinates +- Updates UMAP DID documents (kind 30800) +- Synchronizes UMAP events across constellation +- Manages ORE contracts for UMAPs + +**`UPLANET.refresh.sh`** (daily cron): +- Refreshes SECTOR/REGION keys from coordinates +- Updates economic flows (Ẑen distribution) +- Synchronizes constellation events +- Manages smart contracts for geographic grids + +**Key Properties:** +- Geographic keys are ephemeral (regenerated daily) +- Smart contracts ensure key consistency across network +- Daily refresh prevents key staleness +- Constellation sync ensures network-wide consistency + +## Compatibility + +This extension is compatible with: +- ✅ [NIP-42](42.md) - Authentication to relays +- ✅ [NIP-01](01.md) - Basic protocol flow +- ✅ [NIP-101](101.md) - UPlanet Identity & Twin-Key system +- ✅ [NIP-49](49.md) - Private key encryption (SSSS alternative) + +Standard NIP-42 relays can accept the personal authentication and ignore UMAP authentication. + +## Reference Implementation + +- **Backend:** `UPassport/54321.py` (FastAPI with NIP-42 validation) +- **Frontend:** `UPlanet/earth/common.js` (JavaScript Twin-Key derivation) +- **Terminal:** `UPassport/scan_new.html` (QR code scanner) +- **Script:** `UPassport/upassport.sh` (SSSS reconstruction) +- **Keygen:** `Astroport.ONE/tools/keygen` (Deterministic key generation) +- **MULTIPASS:** `Astroport.ONE/tools/make_NOSTRCARD.sh` (SSSS splitting) +- **Refresh:** `Astroport.ONE/RUNTIME/NOSTR.UMAP.refresh.sh` (Daily UMAP refresh) +- **Refresh:** `Astroport.ONE/RUNTIME/UPLANET.refresh.sh` (Daily constellation refresh) +- **Relay:** `NIP-101/relay.writePolicy.plugin/` (strfry plugin for validation) +- **Repository:** [github.com/papiche/NIP-101](https://github.com/papiche/NIP-101) + +## License + +This specification is released under **AGPL-3.0**. + +## Authors + +- **papiche** - [github.com/papiche](https://github.com/papiche) +- **CopyLaRadio SCIC** - Cooperative implementation +- **Astroport.ONE community** - UPlanet architecture diff --git a/58-oracle-badges-extension.md b/58-oracle-badges-extension.md new file mode 100644 index 0000000000..f187ad239e --- /dev/null +++ b/58-oracle-badges-extension.md @@ -0,0 +1,394 @@ +NIP-58 Oracle Badges Extension +=============================== + +Gamification of Competence Certification via NIP-58 Badges +---------------------------------------------------------- + +`draft` `extension` `optional` + +This document describes an extension to [NIP-58](58.md) that integrates **badge emission** into the UPlanet Oracle System. This extension enables visual gamification of competence certification, making validated skills and achievements visible on user profiles across Nostr clients. + +## Overview + +This extension adds automatic badge emission when Oracle credentials (kind 30503) are issued, creating a **visual representation of validated competencies** that can be displayed in user profiles, enhancing the gamification aspect of the Web of Trust system. + +## Motivation + +The Oracle System (NIP-42 extension) issues W3C Verifiable Credentials (kind 30503) for validated competencies, but these credentials are: +- ❌ Not visually represented in user profiles +- ❌ Not easily discoverable by other users +- ❌ Not gamified for user engagement + +This extension solves these problems by: +- ✅ Automatically emitting NIP-58 badges when credentials are issued +- ✅ Creating visual representations of competencies +- ✅ Enabling badge display in user profiles (kind 30008) +- ✅ Supporting gamification of the ecological and competence systems + +## Integration with Oracle System + +### Badge Emission Flow + +When a credential (kind 30503) is issued by the Oracle System: + +1. **Badge Definition (kind 30009)** - Created automatically if it doesn't exist +2. **Badge Award (kind 8)** - Emitted immediately after credential issuance +3. **Profile Badges (kind 30008)** - User can accept/reject and order badges + +### Automatic Badge Creation + +The Oracle System automatically creates badge definitions for: + +#### Official Permits +- `PERMIT_ORE_V1` → Badge: `ore_verifier` +- `PERMIT_DRIVER` → Badge: `driver_license` +- `PERMIT_MEDICAL_FIRST_AID` → Badge: `first_aid_provider` +- `PERMIT_BUILDING_ARTISAN` → Badge: `building_artisan` +- `PERMIT_WOT_DRAGON` → Badge: `wot_dragon` + +#### WoTx2 Auto-Proclaimed Permits +- `PERMIT_*_X1` → Badge: `{permit_name}_x1` +- `PERMIT_*_X2` → Badge: `{permit_name}_x2` +- `PERMIT_*_X3` → Badge: `{permit_name}_x3` +- ... (progression continues to X144+) + +**Badge naming convention:** +- Base name: Permit ID converted to lowercase with underscores +- Level suffix: `_x{n}` for WoTx2 permits +- Example: `PERMIT_MAITRE_NAGEUR_X5` → Badge: `permit_maitre_nageur_x5` + +## Event Structure + +### Badge Definition (kind 30009) + +Created by `UPLANETNAME.G1` (Oracle authority) when a permit is first defined or when a WoTx2 level is created. + +```jsonc +{ + "kind": 30009, + "pubkey": "", + "tags": [ + ["d", "ore_verifier"], + ["name", "ORE Environmental Verifier"], + ["description", "Certified to verify ORE environmental contracts. Awarded after 5 peer attestations."], + ["image", "https://u.copylaradio.com/badges/ore_verifier.png", "1024x1024"], + ["thumb", "https://u.copylaradio.com/badges/ore_verifier_256x256.png", "256x256"], + ["thumb", "https://u.copylaradio.com/badges/ore_verifier_64x64.png", "64x64"], + ["permit_id", "PERMIT_ORE_V1"], + ["t", "uplanet"], + ["t", "oracle"], + ["t", "badge"] + ], + "content": "", + "created_at": , + "sig": "" +} +``` + +**WoTx2 Level Badge Example:** + +```jsonc +{ + "kind": 30009, + "pubkey": "", + "tags": [ + ["d", "permit_maitre_nageur_x5"], + ["name", "Maître Nageur - Niveau X5 (Expert)"], + ["description", "Expert level in swimming instruction. Requires 5 competencies and 5 attestations. Auto-proclaimed mastery with progressive validation."], + ["image", "https://u.copylaradio.com/badges/maitre_nageur_x5.png", "1024x1024"], + ["thumb", "https://u.copylaradio.com/badges/maitre_nageur_x5_256x256.png", "256x256"], + ["permit_id", "PERMIT_MAITRE_NAGEUR_X5"], + ["level", "X5"], + ["label", "Expert"], + ["t", "uplanet"], + ["t", "oracle"], + ["t", "wotx2"], + ["t", "badge"] + ], + "content": "", + "created_at": , + "sig": "" +} +``` + +### Badge Award (kind 8) + +Emitted automatically when a credential (kind 30503) is issued. + +```jsonc +{ + "kind": 8, + "pubkey": "", + "tags": [ + ["a", "30009::ore_verifier"], + ["p", "", "wss://relay.copylaradio.com"], + ["credential_id", ""], + ["permit_id", "PERMIT_ORE_V1"], + ["t", "uplanet"], + ["t", "oracle"] + ], + "content": "Credential issued: PERMIT_ORE_V1", + "created_at": , + "sig": "" +} +``` + +**Link to Credential:** +- The `credential_id` tag references the kind 30503 event ID +- Clients can resolve the full credential via the credential event + +### Profile Badges (kind 30008) + +Users can accept awarded badges and order them in their profile. + +```jsonc +{ + "kind": 30008, + "pubkey": "", + "tags": [ + ["d", "profile_badges"], + ["a", "30009::ore_verifier"], + ["e", "", "wss://relay.copylaradio.com"], + ["a", "30009::permit_maitre_nageur_x5"], + ["e", "", "wss://relay.copylaradio.com"] + ], + "content": "", + "created_at": , + "sig": "" +} +``` + +## Badge Design Guidelines + +### Official Permits +- **Color scheme**: Green gradient (environmental), Blue (professional), Gold (authority) +- **Icon**: Representative of the permit type +- **Text**: Permit name clearly visible + +### WoTx2 Levels +- **X1-X4**: Bronze/Copper color scheme +- **X5-X10 (Expert)**: Silver color scheme +- **X11-X50 (Maître)**: Gold color scheme +- **X51-X100 (Grand Maître)**: Platinum/Diamond color scheme +- **X101+ (Maître Absolu)**: Rainbow/Multicolor gradient + +**Visual progression:** +- Each level shows the number (X1, X2, X3, etc.) +- Level label (Expert, Maître, Grand Maître, Maître Absolu) displayed prominently +- Badge design becomes more elaborate at higher levels + +## Integration with ORE System + +Badges can also be awarded for ORE (Obligations Réelles Environnementales) achievements: + +- **ORE Contract Active**: Badge for UMAPs with active ORE contracts +- **ORE Verified**: Badge for successful ORE contract verification +- **ORE Guardian**: Badge for maintaining ORE compliance over time +- **Biodiversity Champion**: Badge for high biodiversity scores + +These badges are linked to the ORE system (kind 30312, 30313) and UMAP DIDs (kind 30800). + +## Client Implementation + +### Displaying Badges + +Clients should: + +1. **Query badge awards** (kind 8) for a user's pubkey +2. **Resolve badge definitions** (kind 30009) referenced by awards +3. **Check profile badges** (kind 30008) for user's display preferences +4. **Render badges** in user profiles, permit lists, and competence displays + +### Badge Verification + +Clients should verify: +- Badge definition exists (kind 30009) +- Badge award is valid (kind 8, signed by `UPLANETNAME.G1`) +- Corresponding credential exists (kind 30503) +- Credential is not expired or revoked + +### Badge Filtering + +Clients MAY: +- Filter badges by issuer (whitelist `UPLANETNAME.G1`) +- Group badges by category (Official Permits, WoTx2, ORE) +- Show only highest level for WoTx2 permits (X5 instead of X1-X4) +- Allow users to hide/show specific badges + +## Use Cases + +### 1. Visual Competence Display + +Alice has earned `PERMIT_ORE_V1`: +- Oracle issues credential (kind 30503) +- Oracle automatically emits badge award (kind 8) +- Alice's profile shows the "ORE Verifier" badge +- Other users can see Alice's competence at a glance + +### 2. WoTx2 Progression Gamification + +Bob creates "Maître Nageur" mastery: +- Bob earns X1 → Badge `permit_maitre_nageur_x1` awarded +- Bob earns X2 → Badge `permit_maitre_nageur_x2` awarded +- Bob earns X5 → Badge `permit_maitre_nageur_x5` (Expert) awarded +- Bob's profile shows progression: Bronze → Silver → Gold badges + +### 3. ORE Ecological Achievements + +Carol's UMAP has active ORE contract: +- ORE contract verified → Badge `ore_verified` awarded +- High biodiversity score → Badge `biodiversity_champion` awarded +- Carol's profile shows ecological commitment badges + +## API Integration + +The Oracle API (`/api/permit/issue/{request_id}`) automatically: + +1. Issues credential (kind 30503) +2. Creates badge definition (kind 30009) if needed +3. Emits badge award (kind 8) +4. Returns badge information in response + +**API Response Enhancement:** + +```json +{ + "success": true, + "credential_id": "cred_abc123", + "holder_npub": "npub1...", + "permit_id": "PERMIT_ORE_V1", + "badge": { + "badge_id": "ore_verifier", + "badge_definition_event_id": "event_xyz...", + "badge_award_event_id": "event_789...", + "image_url": "https://u.copylaradio.com/badges/ore_verifier.png" + } +} +``` + +## Compatibility + +- ✅ **NIP-58 compliant**: Uses standard NIP-58 event kinds (30009, 8, 30008) +- ✅ **Backward compatible**: Existing credentials (30503) can be retroactively awarded badges +- ✅ **Client agnostic**: Any NIP-58 compatible client can display badges +- ✅ **Optional**: Clients can ignore badges if not supported + +## Implementation Details + +### Backend Implementation + +The badge emission is implemented in `UPassport/oracle_system.py`: + +1. **Function `emit_badge_for_credential()`**: Called automatically after credential issuance + - Generates badge ID from permit ID + - Creates badge definition (kind 30009) if needed + - Emits badge award (kind 8) to credential holder + +2. **Function `_get_badge_id_from_permit()`**: Converts permit IDs to badge IDs + - Official permits: `PERMIT_ORE_V1` → `ore_verifier` + - WoTx2 permits: `PERMIT_MAITRE_NAGEUR_X5` → `permit_maitre_nageur_x5` + +3. **Function `_ensure_badge_definition()`**: Creates/updates badge definitions + - Publishes kind 30009 events with metadata + - Includes permit_id, level, label tags for WoTx2 + +4. **Function `_ensure_badge_definition()`**: Creates/updates badge definitions + - Publishes kind 30009 events with metadata + - Includes permit_id, level, label tags for WoTx2 + - **Automatically generates badge images** using `generate_badge_image.sh` + +5. **Function `_generate_badge_images()`**: Generates badge images automatically + - Uses AI (`question.py`) to create optimized Stable Diffusion prompts + - Generates main image (1024x1024) via ComfyUI + - Creates thumbnails (256x256, 64x64) using ImageMagick + - Uploads all images to IPFS + - Returns IPFS URLs for badge definition + +6. **Function `_emit_badge_award()`**: Publishes badge awards + - Creates kind 8 events referencing badge definitions + - Links to credential via `credential_id` tag + +### Frontend Implementation + +Badge display is implemented in `UPlanet/earth/common.js`: + +1. **Function `fetchBadgeAwards(pubkey)`**: Fetches kind 8 events for a user +2. **Function `fetchBadgeDefinition(badgeId)`**: Fetches kind 30009 events +3. **Function `fetchUserBadges(pubkey)`**: Combines awards + definitions +4. **Function `renderBadge(badge, options)`**: Generates HTML for badge display +5. **Function `displayUserBadges(containerId, pubkey)`**: Displays badges in DOM + +### Interface Integration + +Badges are displayed in three main interfaces: + +1. **oracle.html** (`/oracle`): + - User badges section in "My Permits" tab + - Badge preview for each permit in public list + - Automatic loading after NOSTR connection + +2. **wotx2.html** (`/wotx2`): + - Badge displayed for each certified master in "Maîtres Certifiés" + - Shows badge alongside master information + +3. **plantnet.html** (`/plantnet`): + - Oracle badges section in user dashboard + - Displays alongside Flora achievement badges + - Loads automatically when user stats are loaded + +### Badge Image URLs + +Badge images are automatically generated using AI (Stable Diffusion via ComfyUI): +- **Generation**: Images are created automatically when badge definitions are created +- **Script**: `Astroport.ONE/IA/generate_badge_image.sh` +- **Process**: + 1. AI generates optimized Stable Diffusion prompt using `question.py` + 2. ComfyUI generates badge image (1024x1024) + 3. ImageMagick creates thumbnails (256x256, 64x64) + 4. All images uploaded to IPFS + 5. IPFS URLs stored in badge definition (kind 30009) + +**Image Specifications**: +- Main image: 1024x1024 PNG +- Thumbnail 256: 256x256 PNG +- Thumbnail 64: 64x64 PNG +- Format: PNG with transparency support +- Storage: IPFS (decentralized, permanent) + +**Color Schemes** (automatically applied based on level): +- Official Permits: Green/Blue/Gold gradient +- WoTx2 X1-X4: Bronze/Copper +- WoTx2 X5-X10: Silver +- WoTx2 X11-X50: Gold +- WoTx2 X51-X100: Platinum/Diamond +- WoTx2 X101+: Rainbow/Multicolor gradient + +**Fallback**: If image generation fails, system uses static IPFS paths (images must be manually uploaded). + +## References + +- [NIP-58: Badges](58.md) - Base specification +- [NIP-42 Oracle Permits Extension](42-oracle-permits-extension.md) - Oracle System +- [NIP-101: UPlanet DID & Oracle](101.md) - Full UPlanet protocol +- [ORE System Documentation](../Astroport.ONE/docs/ORE_SYSTEM.md) - ORE contracts +- [Oracle System Documentation](../Astroport.ONE/docs/ORACLE_SYSTEM.md) - Oracle System + +## Implementation Files + +- **Backend**: + - `UPassport/oracle_system.py` (functions: `emit_badge_for_credential`, `_get_badge_id_from_permit`, `_ensure_badge_definition`, `_generate_badge_images`, `_emit_badge_award`) + - `Astroport.ONE/IA/generate_badge_image.sh` (automatic badge image generation with AI) +- **Frontend**: `UPlanet/earth/common.js` (functions: `fetchBadgeAwards`, `fetchBadgeDefinition`, `fetchUserBadges`, `renderBadge`, `displayUserBadges`) +- **Interfaces**: + - `UPassport/templates/oracle.html` + - `UPassport/templates/wotx2.html` + - `UPlanet/earth/plantnet.html` + +--- + +**Status**: Implemented +**Version**: 1.0 +**Author**: UPlanet Development Team +**Date**: 2025-12-01 +**Last Updated**: 2025-12-01 + diff --git a/71-extension.md b/71-extension.md new file mode 100644 index 0000000000..55ffe32b86 --- /dev/null +++ b/71-extension.md @@ -0,0 +1,299 @@ +NIP-71 Extension +================ + +IPFS and info.json Integration for Video Events +------------------------------------------------ + +`draft` `extension` `optional` + +This document describes an extension to [NIP-71](71.md) that leverages IPFS for decentralized video storage and uses `info.json` files for rich metadata storage, including animated GIF thumbnails. + +## Overview + +This extension enhances NIP-71 video events by: + +1. Using IPFS CIDs instead of HTTP URLs for video content and thumbnails +2. Storing comprehensive metadata in `info.json` files on IPFS +3. Supporting both static thumbnails and animated GIF thumbnails +4. Centralizing metadata extraction in the upload process + +## IPFS Integration + +### Video Content Storage + +Instead of using HTTP URLs in the `imeta` tag `url` field, this extension uses IPFS CIDs with the path format: + +``` +/ipfs/{CID}/{filename} +``` + +Clients should resolve IPFS URLs using their preferred IPFS gateway. The CID alone is sufficient to access the content from any IPFS node. + +### Metadata Storage (info.json) + +Each uploaded file generates an `info.json` file that contains comprehensive metadata about the file. This file is stored on IPFS and its CID is included in the NOSTR event. + +#### info.json Structure + +```json +{ + "file": { + "name": "video.mp4", + "size": 12345678, + "type": "video/mp4", + "hash": "sha256_hash" + }, + "ipfs": { + "cid": "Qm...", + "url": "/ipfs/Qm.../video.mp4", + "date": "2025-01-XX XX:XX +0000" + }, + "image": { + "dimensions": "1920x1080" + }, + "media": { + "duration": 123.456, + "video_codecs": "h264", + "audio_codecs": "aac", + "dimensions": "1920x1080", + "thumbnail_ipfs": "Qm...", + "gifanim_ipfs": "Qm..." + }, + "metadata": { + "description": "Video description", + "type": "video", + "title": "$:/video/Qm.../video.mp4" + }, + "nostr": { + "nip94_tags": [ + ["url", "/ipfs/Qm.../video.mp4"], + ["x", "sha256_hash"], + ["ox", "sha256_hash"], + ["m", "video/mp4"], + ["dim", "1920x1080"] + ] + } +} +``` + +## Thumbnail Support + +This extension supports thumbnails for both video and image content: + +### For Videos + +#### Static Thumbnail (`thumbnail_ipfs`) + +A single frame extracted from the video, typically at 1 second or 10% of the video duration (if longer than 10 seconds). Format: JPEG image stored on IPFS. + +#### Animated GIF Thumbnail (`gifanim_ipfs`) + +An animated GIF extracted from a 1.6-second segment of the video, starting at the golden ratio point (0.618 of video duration). This provides a dynamic preview that gives better context about the video content. + +Format: GIF animation stored on IPFS. + +### For Images + +#### JPG Thumbnail for Non-JPG Images (`thumbnail_ipfs`) + +When uploading images in formats other than JPEG (e.g., PNG, WebP, TIFF), a JPEG thumbnail is automatically generated with: +- **Maximum dimension**: 1200px (aspect ratio preserved) +- **Quality**: 85% +- **EXIF data stripped** for privacy + +For JPEG images, no separate thumbnail is generated as the original can be used directly. + +**Benefits:** +- **Faster loading**: JPG thumbnails are typically smaller than PNG/WebP originals +- **Better compatibility**: JPG is universally supported across all browsers and platforms +- **Bandwidth savings**: Thumbnails use significantly less bandwidth than original images +- **Privacy**: EXIF metadata (including GPS location) is automatically removed + +## NOSTR Event Tags + +### Core Tags for Provenance and Deduplication + +To enable provenance tracking and file deduplication (see [NIP-94 Provenance Extension](94-provenance-extension.md)), video events MUST include: + +```json +{ + "tags": [ + ["url", "/ipfs/QmVideoCID/video.mp4"], + ["x", "sha256_hash_of_file"], + ["m", "video/mp4"], + ["info", "QmInfoJsonCID"], + ["imeta", + "dim 1920x1080", + "url /ipfs/QmVideoCID/video.mp4", + "m video/mp4", + "x sha256_hash_of_file", + "image /ipfs/QmThumbnailCID", + "gifanim /ipfs/QmGifanimCID" + ] + ] +} +``` + +**Critical Tags:** + +- **`["x", "hash"]`**: Direct tag for file hash (SHA-256). This MUST be present as a direct tag (in addition to being in `imeta`) to enable `upload2ipfs.sh` to find existing uploads by hash for deduplication. +- **`["info", "cid"]`**: Reference to `info.json` metadata file. This enables reusing complete metadata from previous uploads without re-extraction. +- **`["upload_chain", "pubkey1,pubkey2,..."]`**: (Optional) Present only on re-uploads. Tracks all users who have shared this file. + +**Note on `ox` tag:** The `ox` tag (original file hash before transformation) is NOT used because files are stored as-is on IPFS without transformations. According to NIP-94, `ox` should only be included when the server modifies the file (compression, resizing, format conversion). Since we don't transform files, `ox` would be redundant with `x`. + +### Complete Example with Provenance + +```json +{ + "tags": [ + ["title", "Video Title"], + ["url", "/ipfs/QmVideoCID/video.mp4"], + ["m", "video/mp4"], + ["x", "sha256_hash_of_file"], + ["info", "QmInfoJsonCID"], + ["thumbnail_ipfs", "QmThumbnailCID"], + ["gifanim_ipfs", "QmGifanimCID"], + ["imeta", + "dim 1920x1080", + "url /ipfs/QmVideoCID/video.mp4", + "m video/mp4", + "x sha256_hash_of_file", + "image /ipfs/QmThumbnailCID", + "gifanim /ipfs/QmGifanimCID" + ], + ["upload_chain", "alice_pubkey,bob_pubkey"], + ["e", "original_event_id", "", "mention"], + ["p", "alice_pubkey"] + ] +} +``` + +### Tag Descriptions + +- **`x`**: SHA-256 hash of the file (REQUIRED for provenance). Must be present as a direct tag AND in `imeta`. +- **`info`**: CID of the `info.json` file containing all metadata (REQUIRED for metadata reuse) +- **`thumbnail_ipfs`**: CID of the static thumbnail image (JPEG for videos, JPG for non-JPG images) +- **`gifanim_ipfs`**: CID of the animated GIF thumbnail (videos only) +- **`upload_chain`**: Comma-separated list of public keys showing the distribution path (present only on re-uploads) +- **`e`**: Reference to the original event (present only on re-uploads) +- **`p`**: Mention of the original author (present only on re-uploads by different users) + +Note: The `imeta` tag `image` field can reference either the static thumbnail or animated GIF. Clients may implement preferences to choose which type to display. + +## Implementation Example + +### Upload Process + +1. User uploads video/image file via `/api/fileupload` +2. File is processed by `upload2ipfs.sh`: + - **File hash is calculated FIRST** (SHA-256, before any IPFS operations) + - **Deduplication check**: Search for existing events with same hash + - If duplicate found: + - Reuse existing CID (skip IPFS upload) + - Download and pin existing metadata via `ipfs get` + - Extend upload chain + - If new file: + - File is added to IPFS + - Metadata is extracted (duration, dimensions, codecs for videos) + - For videos: Static thumbnail and animated GIF are generated + - For non-JPG images: JPG thumbnail is generated + - `info.json` is created with all metadata + - `info.json` is added to IPFS +3. Backend returns JSON with: + - `cid`: Video/image file CID + - `info`: info.json CID + - `fileHash`: SHA-256 hash of file + - `thumbnail_ipfs`: Static/JPG thumbnail CID + - `gifanim_ipfs`: Animated GIF CID (videos only) + - `duration`: Duration in seconds (videos only) + - `dimensions`: Dimensions (e.g., "1920x1080") + - `provenance`: Provenance information (if re-upload) + +### Client Usage + +Clients can: + +1. **Load metadata from info.json**: Fetch `/ipfs/{info_cid}/info.json` to get complete metadata +2. **Display thumbnails**: Use either `thumbnail_ipfs` for static or `gifanim_ipfs` for animated previews +3. **Fallback handling**: If `info.json` is not available, use direct tags from the NOSTR event + +## Benefits + +1. **Decentralization**: Content and metadata stored on IPFS, accessible from any gateway +2. **Rich Metadata**: Comprehensive file information in a single JSON structure +3. **Dynamic Previews**: Animated GIFs provide better context than static thumbnails +4. **Extensibility**: Easy to add new metadata fields to `info.json` without changing NOSTR event structure +5. **Redundancy**: Metadata stored both in NOSTR event tags and `info.json` for maximum compatibility + +## Compatibility + +This extension is backward compatible with standard NIP-71: + +- Standard NIP-71 clients can still parse events using `imeta` tags +- Additional tags (`info`, `thumbnail_ipfs`, `gifanim_ipfs`) are optional +- Clients can choose to load `info.json` for richer metadata or use direct tags +- Static thumbnails follow standard `image` tag conventions in `imeta` + +## Client Recommendations + +1. **Thumbnail Preference**: Allow users to choose between static and animated thumbnails +2. **Caching**: Cache `info.json` contents locally to reduce IPFS lookups +3. **Gateway Selection**: Allow users to configure preferred IPFS gateway +4. **Progressive Enhancement**: Use direct tags if `info.json` is unavailable + +## Example NOSTR Event + +```json +{ + "kind": 21, + "content": "My awesome video", + "tags": [ + ["title", "My Awesome Video"], + ["url", "/ipfs/QmVideoCID/video.mp4"], + ["x", "abc123def456..."], + ["m", "video/mp4"], + ["info", "QmInfoJsonCID"], + ["thumbnail_ipfs", "QmThumbnailCID"], + ["gifanim_ipfs", "QmGifanimCID"], + ["imeta", + "dim 1920x1080", + "url /ipfs/QmVideoCID/video.mp4", + "m video/mp4", + "x abc123def456...", + "duration 123.456", + "image /ipfs/QmThumbnailCID", + "gifanim /ipfs/QmGifanimCID" + ], + ["t", "VideoChannel"], + ["t", "WebcamRecording"] + ] +} +``` + +**Note:** Following NIP-71 standard, use `kind: 21` for normal videos and `kind: 22` for short videos (< 60 seconds). The direct `["x", "hash"]` tag is CRITICAL for enabling deduplication via provenance tracking. + +## Notes on URL Construction + +This extension uses CID-only storage (`thumbnail_ipfs`, `gifanim_ipfs`) instead of full URLs. This is intentional: + +- **Redundancy Avoidance**: URLs like `/ipfs/{CID}` can be constructed from CIDs when needed +- **Gateway Flexibility**: Clients can choose their preferred IPFS gateway at runtime +- **Storage Efficiency**: Only CIDs are stored in `info.json` and NOSTR event tags +- **URL Construction**: When URLs are needed (e.g., in `imeta` tags), they are constructed as `/ipfs/{CID}` + +Clients should construct full URLs by prepending their gateway URL to `/ipfs/{CID}` when making HTTP requests. + +## Reference Implementation + +This extension is implemented in the following components: + +- [`UPassport/upload2ipfs.sh`](https://github.com/papiche/UPassport/blob/main/upload2ipfs.sh): Centralized metadata extraction, and generation of both thumbnail and animated GIF files from video uploads. +- [`UPassport/54321.py`](https://github.com/papiche/UPassport/blob/main/54321.py): Backend API integration for processing video uploads, managing metadata, and publishing NOSTR events with IPFS links. +- [`UPassport/templates/youtube.html`](https://github.com/papiche/UPassport/blob/main/templates/youtube.html): Client-side UI for video/thumbnail selection and display, letting users choose between static and animated thumbnails. +- [`UPlanet/earth/common.js`](https://github.com/papiche/UPlanet/blob/main/earth/common.js): Frontend logic for handling IPFS uploads and parsing returned metadata. + +Each implementation leverages CID-only storage (`thumbnail_ipfs`, `gifanim_ipfs`), constructing full URLs dynamically as needed by combining the user’s preferred IPFS gateway with the relevant CIDs. + +All implementations use CID-only storage (`thumbnail_ipfs`, `gifanim_ipfs`) and construct URLs dynamically when needed. + diff --git a/71-tmdb-metadata-enrichment-extension.md b/71-tmdb-metadata-enrichment-extension.md new file mode 100644 index 0000000000..7067e2cd8f --- /dev/null +++ b/71-tmdb-metadata-enrichment-extension.md @@ -0,0 +1,257 @@ +NIP-71 TMDB Metadata Enrichment Extension +========================================== + +Community-Driven Metadata Corrections and Enrichments +------------------------------------------------------ + +`draft` `extension` `optional` + +This document describes an extension to [NIP-71](71.md) that enables users to correct, enrich, or update TMDB metadata for video events (kind 21/22) using dedicated enrichment events. This allows community-driven metadata improvements while maintaining attribution and traceability. + +## Overview + +This extension enables: +1. **Metadata corrections**: Users can correct errors in TMDB metadata (wrong title, year, genres, etc.) +2. **Metadata enrichments**: Users can add missing information (actors, production details, etc.) +3. **Author updates**: Original authors can update their own metadata +4. **Community contributions**: Multiple users can contribute improvements +5. **Attribution tracking**: All enrichments are signed and traceable + +## Implementation + +### Enrichment Events (Kind 1986) + +Users publish **kind 1986** events to enrich or correct TMDB metadata for video events. + +**Format:** +```json +{ + "kind": 1986, + "pubkey": "", + "created_at": , + "content": "", + "tags": [ + ["e", "", ""], // Reference to video event (kind 21/22) + ["k", "21"], // Kind of the target event (21 or 22) + ["L", "tmdb.metadata"], // Namespace for TMDB metadata enrichments + ["l", "correction", "tmdb.metadata"], // Type: correction, enrichment, or update + ["p", "", ""] // Optional: reference to video author + ] +} +``` + +**Content Field:** +The `.content` field contains a JSON object with the enriched/corrected TMDB metadata: + +```json +{ + "tmdb": { + "title": "Corrected Title", + "year": "2024", + "genres": ["Action", "Sci-Fi"], + "director": "Director Name", + "overview": "Updated overview...", + "tagline": "Updated tagline", + "vote_average": "8.5", + "vote_count": "1234", + "runtime": "120 minutes", + "certification": "PG-13", + "production_companies": ["Company 1", "Company 2"], + "countries": ["USA", "France"], + "languages": ["English", "French"], + "series_name": "Series Name", // For TV shows + "episode_name": "Episode Name", // For TV shows + "season_number": 1, // For TV shows + "episode_number": 5, // For TV shows + "network": "Network Name", // For TV shows + "status": "Returning Series" // For TV shows + }, + "reason": "Corrected title and added missing genres", // Optional explanation + "source": "tmdb.org" // Optional: source of the correction +} +``` + +### Tag Structure + +**Required Tags:** +- `["e", "", ""]`: Reference to the video event (kind 21 or 22) +- `["k", "21"]` or `["k", "22"]`: Kind of the target video event (helps with filtering) +- `["L", "tmdb.metadata"]`: Namespace indicating TMDB metadata enrichment +- `["l", "", "tmdb.metadata"]`: Type of enrichment: + - `"correction"`: Correcting an error in existing metadata + - `"enrichment"`: Adding missing information + - `"update"`: Updating outdated information + - `"author_update"`: Author updating their own metadata (only if pubkey matches video author) + +**Optional Tags:** +- `["p", "", ""]`: Reference to video author (for notifications) +- `["a", "::"]`: Reference to replaceable enrichment event (if updating a previous enrichment) + +### Replaceable Enrichments (Kind 30001) + +For authors who want to update their own metadata, they can use **kind 30001** (replaceable event) with a `d` tag: + +```json +{ + "kind": 30001, + "pubkey": "", + "created_at": , + "content": "", + "tags": [ + ["d", "tmdb-metadata"], // Identifier for this replaceable event + ["e", "", ""], + ["k", "21"], + ["L", "tmdb.metadata"], + ["l", "author_update", "tmdb.metadata"] + ] +} +``` + +**Note:** Only the video author (matching pubkey) should use kind 30001. Other users should use kind 1986 (non-replaceable) to allow multiple enrichments. + +### Client Behavior + +#### Loading Enrichments + +1. **Query enrichments**: Fetch all kind 1986 events with: + - `#e` tag matching the video event ID + - `#L` tag matching `"tmdb.metadata"` + - `#k` tag matching `"21"` or `"22"` + +2. **Query author updates**: If video author matches current user, also fetch kind 30001 events with: + - `#d` tag matching `"tmdb-metadata"` + - `#e` tag matching the video event ID + +3. **Merge enrichments**: Combine all enrichments with original metadata: + - Start with metadata from `info.json` (via `info` tag in video event) + - Apply enrichments in chronological order (oldest first) + - For replaceable events (kind 30001), use only the latest version + - For conflicts, prefer: + 1. Author updates (kind 30001) + 2. Most recent enrichment (kind 1986) + 3. Original metadata + +#### Displaying Enrichments + +1. **Show enrichment indicators**: Display badges showing: + - Number of enrichments + - Whether author has updated metadata + - Most recent enrichment date + +2. **Show enrichment history**: Allow users to view: + - All enrichments with author and timestamp + - Type of each enrichment (correction, enrichment, update) + - Explanation from content field + +3. **Allow contributions**: Provide UI for users to: + - Submit corrections + - Add missing information + - Explain their enrichment + +### Example Events + +#### Correction by Community Member + +```json +{ + "kind": 1986, + "pubkey": "bob_pubkey", + "created_at": 1704067200, + "content": "{\"tmdb\":{\"title\":\"Corrected Title\",\"year\":\"2024\",\"genres\":[\"Action\",\"Sci-Fi\"]},\"reason\":\"Original title was misspelled\"}", + "tags": [ + ["e", "video_event_id_abc123", "wss://relay.example.com"], + ["k", "21"], + ["L", "tmdb.metadata"], + ["l", "correction", "tmdb.metadata"], + ["p", "alice_pubkey", "wss://relay.example.com"] + ] +} +``` + +#### Author Update (Replaceable) + +```json +{ + "kind": 30001, + "pubkey": "alice_pubkey", + "created_at": 1704153600, + "content": "{\"tmdb\":{\"title\":\"Updated Title\",\"genres\":[\"Action\",\"Sci-Fi\",\"Thriller\"],\"overview\":\"Updated description\"}}", + "tags": [ + ["d", "tmdb-metadata"], + ["e", "video_event_id_abc123", "wss://relay.example.com"], + ["k", "21"], + ["L", "tmdb.metadata"], + ["l", "author_update", "tmdb.metadata"] + ] +} +``` + +#### Enrichment (Adding Missing Info) + +```json +{ + "kind": 1986, + "pubkey": "charlie_pubkey", + "created_at": 1704240000, + "content": "{\"tmdb\":{\"director\":\"Director Name\",\"production_companies\":[\"Company 1\"],\"countries\":[\"USA\"]},\"reason\":\"Added missing production details\",\"source\":\"tmdb.org\"}", + "tags": [ + ["e", "video_event_id_abc123", "wss://relay.example.com"], + ["k", "21"], + ["L", "tmdb.metadata"], + ["l", "enrichment", "tmdb.metadata"], + ["p", "alice_pubkey", "wss://relay.example.com"] + ] +} +``` + +## Benefits + +1. **Community-driven improvements**: Users can contribute better metadata +2. **Error correction**: Mistakes can be fixed by the community +3. **Attribution**: All enrichments are signed and traceable +4. **Flexibility**: Multiple enrichments can coexist +5. **Author control**: Authors can update their own metadata using replaceable events +6. **Transparency**: Full history of all enrichments is available +7. **Standards compliance**: Uses existing NIP-32 (Labeling) and NIP-33 (Replaceable Events) patterns + +## Privacy Considerations + +- Enrichments reveal who contributed metadata +- Users who want privacy can use throwaway keys +- Authors can choose to accept or ignore community enrichments + +## Security Considerations + +- All enrichments are signed (NOSTR standard) +- Clients should verify signatures before displaying enrichments +- Authors can publish their own updates to override community enrichments +- Malicious enrichments can be ignored by clients (author updates take precedence) + +## Compatibility + +This extension is backward compatible with NIP-71: +- Standard NIP-71 clients can ignore enrichment events +- Video events (kind 21/22) remain unchanged +- Enrichments are optional and additive +- Original metadata in `info.json` is preserved + +## Client Recommendations + +1. **Default behavior**: Show merged metadata (original + enrichments) +2. **User preference**: Allow users to choose: + - Show only original metadata + - Show only author updates + - Show merged metadata (default) +3. **Enrichment indicators**: Display badges showing number of enrichments +4. **Contribution UI**: Provide easy way to submit enrichments +5. **History view**: Show all enrichments with timestamps and authors +6. **Verification**: Verify signatures before displaying enrichments + +## Future Enhancements + +1. **Voting system**: Allow users to vote on enrichments (most trusted wins) +2. **Reputation**: Track users' contribution quality +3. **Automated verification**: Cross-check enrichments with TMDB API +4. **Bulk enrichments**: Allow enriching multiple videos at once +5. **Enrichment templates**: Pre-filled forms for common enrichments + diff --git a/71-video-user-tags-extension.md b/71-video-user-tags-extension.md new file mode 100644 index 0000000000..9af880f45d --- /dev/null +++ b/71-video-user-tags-extension.md @@ -0,0 +1,770 @@ +NIP-71 Video User Tags Extension +================================= + +User-Generated Tags for Video Events +------------------------------------- + +`draft` `extension` `optional` + +This document describes an extension to [NIP-71](71.md) that enables users to add tags to video events (kind 21/22) using [NIP-32](32.md) Labeling. This allows community-driven categorization, tag clouds, and tag-based search functionality. + +## Overview + +This extension enables: +1. **User-generated tags**: Any user can add tags to any video +2. **Tag aggregation**: Build tag clouds showing most popular tags +3. **Tag-based search**: Find videos by user-contributed tags +4. **Community categorization**: Collective organization of video content + +## Implementation + +### Tag Events (NIP-32) + +Users add tags to videos by publishing **kind 1985** events (NIP-32 Labeling) that reference the video event. + +**Format:** +```json +{ + "kind": 1985, + "pubkey": "", + "created_at": , + "content": "", // Optional: explanation for the tag + "tags": [ + ["L", "ugc"], // User-generated content namespace (NIP-32) + ["l", "bitcoin", "ugc"], // Tag value with namespace mark + ["e", "", ""], // Reference to video event + ["k", "21"] // Kind of the target event (21 or 22) + ] +} +``` + +### Tag Structure + +**Required Tags:** +- `["e", "", ""]`: Reference to the video event (kind 21 or 22) +- `["l", "", "ugc"]`: The tag value (lowercase, alphanumeric + hyphens/underscores) +- `["L", "ugc"]`: Namespace indicating user-generated content (NIP-32) + +**Optional Tags:** +- `["k", "21"]` or `["k", "22"]`: Kind of the target video event (helps with filtering) +- `["p", "", ""]`: Reference to video author (optional, for notifications) + +**Content Field:** +- Can contain explanation or context for the tag +- Empty string is acceptable + +### Tag Naming Conventions + +**Recommended:** +- Use lowercase: `bitcoin`, `tutorial`, `music` +- Use hyphens for multi-word: `machine-learning`, `web-development` +- Use underscores for compound: `video_game`, `how_to` +- Keep tags concise (1-3 words max) +- Avoid special characters except hyphens and underscores + +**Examples:** +- ✅ `bitcoin`, `crypto`, `tutorial`, `music`, `comedy` +- ✅ `machine-learning`, `web-development`, `cooking-tips` +- ❌ `Bitcoin` (should be lowercase) +- ❌ `bitcoin tutorial` (use hyphen: `bitcoin-tutorial`) +- ❌ `bitcoin!` (no special chars) + +### Multiple Tags per User + +A single user can add multiple tags to the same video by publishing multiple kind 1985 events: + +```json +// Event 1: Add "bitcoin" tag +{ + "kind": 1985, + "tags": [ + ["L", "ugc"], + ["l", "bitcoin", "ugc"], + ["e", "", ""], + ["k", "21"] + ] +} + +// Event 2: Add "tutorial" tag +{ + "kind": 1985, + "tags": [ + ["L", "ugc"], + ["l", "tutorial", "ugc"], + ["e", "", ""], + ["k", "21"] + ] +} +``` + +### Removing Tags + +To remove a tag, users can publish a [NIP-09](09.md) deletion event targeting their own kind 1985 tag event: + +```json +{ + "kind": 5, + "tags": [["e", ""]], + "content": "deleted tag" +} +``` + +## Tag Aggregation + +### Building Tag Clouds + +Clients aggregate all kind 1985 events referencing video events to build tag statistics: + +**Query:** +```javascript +// Fetch all tag events for videos +const filter = { + kinds: [1985], + "#L": ["ugc"], + "#k": ["21", "22"] // Only tags for video events +}; + +// Group by tag value and count +const tagCounts = {}; +tagEvents.forEach(event => { + const tagValue = event.tags.find(t => t[0] === 'l')?.[1]; + if (tagValue) { + tagCounts[tagValue] = (tagCounts[tagValue] || 0) + 1; + } +}); +``` + +**Tag Cloud Data Structure:** +```json +{ + "bitcoin": 42, + "tutorial": 38, + "music": 35, + "comedy": 28, + "cooking": 15, + ... +} +``` + +### Per-Video Tag Aggregation + +For a specific video, aggregate all tags: + +**Query:** +```javascript +const filter = { + kinds: [1985], + "#e": [videoEventId], + "#L": ["ugc"] +}; + +// Result: Array of tag events +// Extract unique tag values and count occurrences +const videoTags = {}; +tagEvents.forEach(event => { + const tagValue = event.tags.find(t => t[0] === 'l')?.[1]; + const taggerPubkey = event.pubkey; + if (tagValue) { + if (!videoTags[tagValue]) { + videoTags[tagValue] = { + count: 0, + taggers: [] + }; + } + videoTags[tagValue].count++; + videoTags[tagValue].taggers.push(taggerPubkey); + } +}); +``` + +**Result:** +```json +{ + "bitcoin": { + "count": 5, + "taggers": ["pubkey1", "pubkey2", "pubkey3", "pubkey4", "pubkey5"] + }, + "tutorial": { + "count": 3, + "taggers": ["pubkey1", "pubkey6", "pubkey7"] + } +} +``` + +## Tag-Based Search + +### Finding Videos by Tag + +**Query Pattern:** +```javascript +// Step 1: Find all tag events with specific tag +const tagFilter = { + kinds: [1985], + "#l": ["bitcoin"], // Tag value + "#L": ["ugc"] +}; + +// Step 2: Extract video event IDs from tag events +const videoEventIds = tagEvents + .map(event => event.tags.find(t => t[0] === 'e')?.[1]) + .filter(Boolean); + +// Step 3: Fetch video events +const videoFilter = { + kinds: [21, 22], + ids: videoEventIds +}; +``` + +### Multi-Tag Search (AND) + +Find videos tagged with multiple tags: + +```javascript +// Find videos tagged with both "bitcoin" AND "tutorial" +const tag1Events = await fetchTagEvents("bitcoin"); +const tag2Events = await fetchTagEvents("tutorial"); + +const videoIds1 = extractVideoIds(tag1Events); +const videoIds2 = extractVideoIds(tag2Events); + +// Intersection +const commonVideoIds = videoIds1.filter(id => videoIds2.includes(id)); +``` + +### Multi-Tag Search (OR) + +Find videos tagged with any of multiple tags: + +```javascript +// Find videos tagged with "bitcoin" OR "ethereum" +const allVideoIds = new Set(); +const tag1Events = await fetchTagEvents("bitcoin"); +const tag2Events = await fetchTagEvents("ethereum"); + +[...tag1Events, ...tag2Events].forEach(event => { + const videoId = event.tags.find(t => t[0] === 'e')?.[1]; + if (videoId) allVideoIds.add(videoId); +}); +``` + +## Backend Implementation + +### API Endpoint: GET /api/video/tags + +Aggregate tags for videos and return tag cloud statistics. + +**Query Parameters:** +- `video_id` (optional): Specific video event ID +- `limit` (optional): Number of top tags to return (default: 50) +- `min_count` (optional): Minimum tag count to include (default: 1) + +**Response:** +```json +{ + "success": true, + "tag_cloud": { + "bitcoin": 42, + "tutorial": 38, + "music": 35 + }, + "total_tags": 150, + "unique_videos": 75 +} +``` + +### API Endpoint: GET /api/video/tags/{video_id} + +Get all tags for a specific video. + +**Response:** +```json +{ + "success": true, + "video_id": "abc123...", + "tags": { + "bitcoin": { + "count": 5, + "taggers": ["pubkey1", "pubkey2", ...] + }, + "tutorial": { + "count": 3, + "taggers": ["pubkey1", ...] + } + }, + "total_tag_count": 8, + "unique_tags": 2 +} +``` + +### API Endpoint: GET /youtube?tag={tag_value} + +Extend existing `/youtube` endpoint to filter by user tags. + +**Query Parameters:** +- `tag`: Tag value to filter by +- `tags`: Comma-separated list for OR search (e.g., `tags=bitcoin,ethereum`) +- `tags_and`: Comma-separated list for AND search (e.g., `tags_and=bitcoin,tutorial`) + +**Response:** Same format as `/youtube` but filtered by tags. + +## Frontend Implementation + +### JavaScript Functions + +#### Add Tag to Video + +```javascript +/** + * Add a user tag to a video + * @param {string} videoEventId - Video event ID (kind 21 or 22) + * @param {string} tagValue - Tag value (lowercase, alphanumeric) + * @param {string} videoAuthorPubkey - Video author's pubkey (optional) + * @param {string} relayUrl - Relay URL where video is stored + * @returns {Promise} Result object + */ +async function addVideoTag(videoEventId, tagValue, videoAuthorPubkey = null, relayUrl = null) { + // Validate tag format + if (!/^[a-z0-9_-]+$/.test(tagValue)) { + throw new Error('Invalid tag format. Use lowercase alphanumeric with hyphens/underscores.'); + } + + // Ensure NOSTR connection + const pubkey = await ensureNostrConnection(); + if (!pubkey) { + throw new Error('NOSTR connection required'); + } + + // Build tags + const tags = [ + ['L', 'ugc'], + ['l', tagValue.toLowerCase(), 'ugc'], + ['e', videoEventId, relayUrl || window.relayUrl] + ]; + + // Add kind tag if we know the video kind + // (Could be determined by fetching the video event first) + // tags.push(['k', '21']); // or '22' + + // Add author pubkey if provided + if (videoAuthorPubkey) { + tags.push(['p', videoAuthorPubkey, relayUrl || window.relayUrl]); + } + + // Publish tag event + const result = await publishNote('', tags, 1985, { + silent: false + }); + + return { + success: result.success, + tagEventId: result.eventId, + tagValue: tagValue + }; +} +``` + +#### Remove Tag from Video + +```javascript +/** + * Remove a user tag from a video + * @param {string} tagEventId - The kind 1985 event ID to delete + * @returns {Promise} Result object + */ +async function removeVideoTag(tagEventId) { + const pubkey = await ensureNostrConnection(); + if (!pubkey) { + throw new Error('NOSTR connection required'); + } + + // Publish deletion event (NIP-09) + const result = await publishNote('deleted tag', [['e', tagEventId]], 5, { + silent: false + }); + + return { + success: result.success, + deletedEventId: tagEventId + }; +} +``` + +#### Fetch Tags for Video + +```javascript +/** + * Fetch all tags for a video + * @param {string} videoEventId - Video event ID + * @param {number} timeout - Timeout in ms (default: 5000) + * @returns {Promise} Tags object with counts and taggers + */ +async function fetchVideoTags(videoEventId, timeout = 5000) { + await connectToRelay(); + + if (!window.nostrRelay) { + throw new Error('Relay not connected'); + } + + const filter = { + kinds: [1985], + '#e': [videoEventId], + '#L': ['ugc'] + }; + + const tagEvents = await window.nostrRelay.list([filter], { timeout }); + + // Aggregate tags + const tags = {}; + tagEvents.forEach(event => { + const tagValue = event.tags.find(t => t[0] === 'l')?.[1]; + if (tagValue) { + if (!tags[tagValue]) { + tags[tagValue] = { + count: 0, + taggers: [], + events: [] + }; + } + tags[tagValue].count++; + tags[tagValue].taggers.push(event.pubkey); + tags[tagValue].events.push(event.id); + } + }); + + return tags; +} +``` + +#### Fetch Tag Cloud + +```javascript +/** + * Fetch tag cloud statistics + * @param {number} limit - Number of top tags to return (default: 50) + * @param {number} minCount - Minimum tag count (default: 1) + * @returns {Promise} Tag cloud object + */ +async function fetchTagCloud(limit = 50, minCount = 1) { + await connectToRelay(); + + if (!window.nostrRelay) { + throw new Error('Relay not connected'); + } + + const filter = { + kinds: [1985], + '#L': ['ugc'], + '#k': ['21', '22'] // Only tags for video events + }; + + const tagEvents = await window.nostrRelay.list([filter], { timeout: 10000 }); + + // Aggregate tag counts + const tagCounts = {}; + const videoIds = new Set(); + + tagEvents.forEach(event => { + const tagValue = event.tags.find(t => t[0] === 'l')?.[1]; + const videoId = event.tags.find(t => t[0] === 'e')?.[1]; + + if (tagValue && videoId) { + tagCounts[tagValue] = (tagCounts[tagValue] || 0) + 1; + videoIds.add(videoId); + } + }); + + // Filter by minCount and sort + const filtered = Object.entries(tagCounts) + .filter(([tag, count]) => count >= minCount) + .sort((a, b) => b[1] - a[1]) + .slice(0, limit) + .reduce((obj, [tag, count]) => { + obj[tag] = count; + return obj; + }, {}); + + return { + tags: filtered, + totalTags: tagEvents.length, + uniqueVideos: videoIds.size + }; +} +``` + +#### Search Videos by Tag + +```javascript +/** + * Search videos by tag(s) + * @param {string|Array} tags - Tag value(s) to search for + * @param {string} operator - 'AND' or 'OR' (default: 'OR') + * @returns {Promise>} Array of video event IDs + */ +async function searchVideosByTag(tags, operator = 'OR') { + await connectToRelay(); + + if (!window.nostrRelay) { + throw new Error('Relay not connected'); + } + + const tagArray = Array.isArray(tags) ? tags : [tags]; + + if (operator === 'AND') { + // Find intersection + const videoIdSets = await Promise.all( + tagArray.map(async tag => { + const filter = { + kinds: [1985], + '#l': [tag], + '#L': ['ugc'] + }; + const events = await window.nostrRelay.list([filter], { timeout: 5000 }); + return new Set(events.map(e => e.tags.find(t => t[0] === 'e')?.[1]).filter(Boolean)); + }) + ); + + // Intersection + let result = videoIdSets[0]; + for (let i = 1; i < videoIdSets.length; i++) { + result = new Set([...result].filter(id => videoIdSets[i].has(id))); + } + return Array.from(result); + } else { + // Find union (OR) + const allVideoIds = new Set(); + await Promise.all( + tagArray.map(async tag => { + const filter = { + kinds: [1985], + '#l': [tag], + '#L': ['ugc'] + }; + const events = await window.nostrRelay.list([filter], { timeout: 5000 }); + events.forEach(event => { + const videoId = event.tags.find(t => t[0] === 'e')?.[1]; + if (videoId) allVideoIds.add(videoId); + }); + }) + ); + return Array.from(allVideoIds); + } +} +``` + +### UI Components + +#### Tag Input Component + +```javascript +/** + * Display tag input and existing tags for a video + * @param {string} containerId - Container element ID + * @param {string} videoEventId - Video event ID + */ +async function displayVideoTags(containerId, videoEventId) { + const container = document.getElementById(containerId); + + // Fetch existing tags + const tags = await fetchVideoTags(videoEventId); + + // Display existing tags + const tagsHtml = Object.entries(tags) + .sort((a, b) => b[1].count - a[1].count) + .map(([tag, data]) => ` + + ${tag} (${data.count}) + ${data.taggers.includes(window.userPubkey) ? + '' : + ''} + + `).join(''); + + // Tag input + const inputHtml = ` +
+ + +
+ `; + + container.innerHTML = inputHtml + '
' + tagsHtml + '
'; +} + +async function addTag(videoEventId) { + const input = document.getElementById(`tag-input-${videoEventId}`); + const tagValue = input.value.trim().toLowerCase(); + + if (!tagValue || !/^[a-z0-9_-]+$/.test(tagValue)) { + showNotification({ + message: 'Invalid tag format. Use lowercase alphanumeric with hyphens/underscores.', + type: 'error' + }); + return; + } + + try { + const result = await addVideoTag(videoEventId, tagValue); + if (result.success) { + showNotification({ + message: `Tag "${tagValue}" added successfully!`, + type: 'success' + }); + input.value = ''; + // Refresh tags display + await displayVideoTags(`tags-${videoEventId}`, videoEventId); + } + } catch (error) { + showNotification({ + message: 'Error adding tag: ' + error.message, + type: 'error' + }); + } +} + +async function removeTag(tagEventId) { + try { + const result = await removeVideoTag(tagEventId); + if (result.success) { + showNotification({ + message: 'Tag removed successfully!', + type: 'success' + }); + // Refresh tags display + const videoEventId = /* extract from context */; + await displayVideoTags(`tags-${videoEventId}`, videoEventId); + } + } catch (error) { + showNotification({ + message: 'Error removing tag: ' + error.message, + type: 'error' + }); + } +} +``` + +#### Tag Cloud Component + +```javascript +/** + * Display tag cloud + * @param {string} containerId - Container element ID + * @param {number} limit - Number of tags to display (default: 50) + */ +async function displayTagCloud(containerId, limit = 50) { + const container = document.getElementById(containerId); + + const tagCloud = await fetchTagCloud(limit, 1); + + // Calculate font sizes based on counts + const maxCount = Math.max(...Object.values(tagCloud.tags)); + const minCount = Math.min(...Object.values(tagCloud.tags)); + const sizeRange = 24 - 12; // max 24px, min 12px + + const tagsHtml = Object.entries(tagCloud.tags) + .map(([tag, count]) => { + const size = 12 + (count - minCount) / (maxCount - minCount) * sizeRange; + return ` + + ${tag} (${count}) + + `; + }).join(' '); + + container.innerHTML = ` +
+ ${tagsHtml} +
+

+ ${tagCloud.totalTags} tags across ${tagCloud.uniqueVideos} videos +

+ `; +} +``` + +## Integration with Existing System + +### Backend (54321.py) + +Add endpoints to `/youtube` route: + +```python +# In /youtube endpoint, add tag filtering +tag_filter = request.args.get('tag') +tags_or = request.args.get('tags') # Comma-separated OR +tags_and = request.args.get('tags_and') # Comma-separated AND + +if tag_filter or tags_or or tags_and: + # Query kind 1985 events for tags + # Extract video event IDs + # Filter videos by those IDs + pass +``` + +### Frontend (youtube.html) + +Add tag cloud sidebar and tag display on video cards: + +```html + +
+
Popular Tags
+
+
+ + +
+``` + +## Benefits + +✅ **Community-Driven**: Users collectively organize content +✅ **Discoverability**: Tag-based search improves content discovery +✅ **Flexibility**: Users can add any relevant tags +✅ **Standards-Compliant**: Uses existing NIP-32 standard +✅ **Decentralized**: No central authority controls tags +✅ **Removable**: Users can delete their own tags +✅ **Aggregatable**: Tag clouds show community preferences + +## Privacy Considerations + +- Tag events reveal which users tagged which videos +- Users who want privacy can use different pubkeys for tagging +- Tag events are public and searchable +- Consider implementing anonymous tagging option (future enhancement) + +## Future Enhancements + +1. **Tag Suggestions**: Auto-suggest tags based on video content/metadata +2. **Tag Moderation**: Allow video authors to remove inappropriate tags +3. **Tag Categories**: Organize tags into categories (topic, genre, language, etc.) +4. **Weighted Tags**: Weight tags by tagger's reputation or network +5. **Tag Synonyms**: Map similar tags (e.g., "crypto" = "cryptocurrency") +6. **Tag Hierarchies**: Support parent-child tag relationships +7. **Anonymous Tagging**: Option to tag without revealing pubkey + +## Compatibility + +This extension is fully compatible with: +- **NIP-32**: Uses standard labeling format +- **NIP-71**: Extends video events without modifying them +- **NIP-09**: Tag removal via deletion events +- **NIP-01**: Standard event structure and tags + +Standard NOSTR clients can ignore kind 1985 events if they don't support this extension. + +## References + +- [NIP-32: Labeling](32.md) +- [NIP-71: Video Events](71.md) +- [NIP-09: Event Deletion](09.md) +- [NIP-01: Basic Protocol](01.md) + diff --git a/94-provenance-extension.md b/94-provenance-extension.md new file mode 100644 index 0000000000..566a733552 --- /dev/null +++ b/94-provenance-extension.md @@ -0,0 +1,306 @@ +NIP-94 Provenance Extension +========================== + +File Provenance and Upload Chain Tracking +------------------------------------------ + +`draft` `extension` `optional` + +This document describes an extension to [NIP-94](94.md) that implements **provenance tracking** and **upload chain** for file metadata events. This allows NOSTR to track the origin and distribution history of files across the network. + +## Overview + +This extension enhances NIP-94 file metadata events by: + +1. **Detecting duplicate files** by their hash (SHA256) +2. **Tracking the original author** of a file +3. **Creating an upload chain** showing all users who have shared the file +4. **Referencing the original event** to maintain attribution + +## Motivation + +When users share files on NOSTR: +- The same file may be uploaded multiple times by different users +- Attribution to the original creator is lost +- There's no way to track how content spreads across the network +- Copyright and provenance information is not preserved + +This extension solves these problems by: +- Creating a verifiable chain of custody +- Ensuring original creators are always credited +- Building a social graph of content distribution +- Preventing unnecessary duplication of file storage + +## Implementation + +### Upload Process with Provenance Tracking + +When a file is uploaded to IPFS via `upload2ipfs.sh`: + +1. **Calculate file hash** (SHA256) - **BEFORE** any IPFS operations +2. **Search NOSTR relay** for existing NIP-94 events with the same hash (tag `x`) +3. **If found (file already exists):** + - Extract original event ID and author + - Extract original CID from the event's `url` tag + - **Reuse existing CID** (no upload to IPFS) + - Download and pin the existing file locally using `ipfs get` (helps distribute) + - Fetch existing `info.json` metadata via `ipfs get` + - Reuse all metadata (duration, dimensions, thumbnails, etc.) + - Check if there's an existing `upload_chain` tag + - Extend the chain or create a new one + - Add provenance tags to the new event + - **Result:** No redundant upload, instant processing (~1-2 seconds vs minutes) +4. **If not found:** + - This is the first upload (original) + - Upload file to IPFS normally + - Generate thumbnails and metadata + - No provenance tags needed + +### New Tags + +#### `upload_chain` Tag + +Format: `["upload_chain", "pubkey1,pubkey2,pubkey3"]` + +- **pubkey1**: Original uploader (first person to share this file) +- **pubkey2**: Second person to share +- **pubkey3**: Current uploader +- And so on... + +The chain is **ordered** and **cumulative**: each new uploader is appended to the chain. + +#### Provenance Tags + +When a file is re-uploaded, the following standard NIP tags are added: + +- `["e", "", "", "mention"]`: Reference to the original NIP-94 event +- `["p", ""]`: Mention of the original author (if different from current user) + +### Example Events + +#### Original Upload (First Time) + +```json +{ + "kind": 1063, + "pubkey": "alice_pubkey", + "content": "", + "tags": [ + ["url", "https://ipfs.io/ipfs/QmABC123.../file.pdf"], + ["x", "sha256_hash_of_file"], + ["m", "application/pdf"], + ["info", "QmInfoCID"] + ] +} +``` + +**Note:** Only the `x` tag is used. The `ox` tag (original file hash before transformation) is NOT included because `upload2ipfs.sh` does not transform files. According to NIP-94, `ox` should only be used when the server modifies the file (compression, resizing, format conversion, etc.). Since we store files as-is on IPFS, `ox` would be redundant with `x`. + +#### Re-Upload by Another User + +```json +{ + "kind": 1063, + "pubkey": "bob_pubkey", + "content": "", + "tags": [ + ["url", "https://ipfs.io/ipfs/QmABC123.../file.pdf"], + ["x", "sha256_hash_of_file"], + ["m", "application/pdf"], + ["e", "original_event_id", "", "mention"], + ["p", "alice_pubkey"], + ["upload_chain", "alice_pubkey,bob_pubkey"], + ["info", "QmInfoCID"] + ] +} +``` + +**Note:** The URL uses the **same CID** (`QmABC123`) as the original event. The file is not re-uploaded to IPFS; instead, the existing CID is reused. Bob's node pins the content locally to help distribute it across the IPFS network. + +#### Third Re-Upload + +```json +{ + "kind": 1063, + "pubkey": "charlie_pubkey", + "content": "", + "tags": [ + ["url", "https://ipfs.io/ipfs/QmABC123.../file.pdf"], + ["x", "sha256_hash_of_file"], + ["m", "application/pdf"], + ["e", "original_event_id", "", "mention"], + ["p", "alice_pubkey"], + ["upload_chain", "alice_pubkey,bob_pubkey,charlie_pubkey"], + ["info", "QmInfoCID"] + ] +} +``` + +**Note:** The URL still uses the **same CID** (`QmABC123`) as the original. Charlie's node also pins the content locally. The network now has three nodes (Alice, Bob, Charlie) distributing this file, making it more resilient and faster to retrieve. + +## info.json Integration + +The provenance information is also stored in the `info.json` file (IPFS metadata): + +```json +{ + "file": { + "hash": "sha256_hash_of_file" + }, + "provenance": { + "original_event_id": "abc123...", + "original_author": "alice_pubkey", + "upload_chain": "alice_pubkey,bob_pubkey,charlie_pubkey", + "is_reupload": true + } +} +``` + +## Client Behavior + +### Uploading Files + +1. Clients SHOULD pass the user's public key (hex format) to the upload script +2. The script calculates the file hash (SHA256) **before** uploading to IPFS +3. The script queries the relay for existing NIP-94 events with matching hash +4. If found: + - The script reuses the existing CID (skips `ipfs add`) + - The script uses `ipfs get` to download and pin the file locally + - The script reuses all metadata from the original `info.json` + - Provenance tags are automatically added + - **Processing time:** ~1-2 seconds (instead of minutes for large files) +5. If not found: + - Normal upload process (ipfs add, metadata extraction, thumbnail generation) + - No provenance tags + +### Displaying Files + +1. Clients SHOULD display the original author's information when showing a file +2. Clients MAY display the full upload chain to show distribution history +3. Clients SHOULD use the `e` tag to link to the original event + +### Searching for Files + +1. Clients can search for all uploads of a specific file by hash (tag `x`) +2. Clients can find the original upload by looking for events without an `e` tag referencing another NIP-94 event +3. Clients can trace the distribution path using the `upload_chain` tag + +## Benefits + +1. **Attribution**: Original creators are always credited +2. **De-duplication**: Complete avoidance of redundant IPFS uploads +3. **Performance**: Re-uploads are instant (~1-2 seconds) instead of minutes +4. **Bandwidth**: Saves massive bandwidth by reusing existing CIDs +5. **Storage**: Prevents redundant storage of duplicate files +6. **Provenance**: Complete history of who shared what +7. **Copyright**: Clear chain of custody for copyright verification +8. **Social Graph**: Visualize how content spreads through the network +9. **Discovery**: Find related uploads of the same content +10. **Distribution**: Each re-uploader helps distribute the file via IPFS pinning + +## Privacy Considerations + +- The `upload_chain` reveals who shared a file +- Users who want privacy SHOULD use a different public key for each upload +- Clients MAY implement an "anonymous upload" mode that omits provenance tracking + +## Security Considerations + +- Provenance is based on file hash (SHA256) +- Collision attacks are theoretically possible but computationally infeasible +- Clients SHOULD verify file hashes before trusting provenance information +- Malicious users could falsely claim to be the original uploader by not including provenance tags (but the actual original event would still exist on the relay) +- The `ipfs get` operation pins content locally, which helps distribute but also stores it on the user's node +- Users should be aware that re-uploading means they become a host for that content on IPFS + +## Reference Implementation + +- `UPassport/upload2ipfs.sh`: Bash script implementing provenance tracking +- `UPassport/54321.py`: Python backend passing user pubkey to upload script +- `Astroport.ONE/tools/nostr_get_events.sh`: NOSTR event query script + +## Technical Details + +### IPFS Operations Order + +The script optimizes by performing operations in this specific order: + +1. **Calculate file hash** (SHA256) - First operation, before any IPFS interaction +2. **Query NOSTR relay** - Search for existing events with matching hash +3. **Conditional IPFS upload:** + - If duplicate found: Skip `ipfs add`, use `ipfs get` to pin existing CID + - If new file: Perform normal `ipfs add` operation +4. **Metadata handling:** + - If duplicate: Fetch existing `info.json` via `ipfs get`, reuse all metadata + - If new file: Generate metadata via ffprobe/ffmpeg + +This order ensures maximum efficiency and prevents redundant operations. + +### IPFS Get vs HTTP Gateway + +The implementation uses `ipfs get` instead of HTTP gateway (`curl`) for several reasons: + +1. **Automatic pinning**: `ipfs get` downloads AND pins in one operation +2. **P2P reliability**: Direct IPFS network access, not dependent on specific gateways +3. **Decentralization**: No reliance on HTTP infrastructure +4. **Performance**: Often faster than HTTP gateway retrieval +5. **Network contribution**: User automatically helps distribute content + +## Future Enhancements + +1. **Licensing Tags**: Add standard licensing tags (CC, MIT, etc.) +2. **Signature Verification**: Verify that the original author signed the file +3. **Timestamp Verification**: Use NIP-03 OpenTimestamps for provable upload times +4. **Content Addressing**: Use NIP-94 tags to reference multiple IPFS gateways +5. **Automatic Attribution**: Clients automatically add attribution when sharing files +6. **Smart Pinning**: Implement LRU cache for pinned content to manage storage +7. **Reputation System**: Track users' contribution to content distribution + +## Performance Characteristics + +### Original Upload (Alice) +- **Time:** ~30-60 seconds (for 50MB video) +- **Bandwidth:** ~50MB upload to IPFS +- **Operations:** ipfs add, ffprobe, ffmpeg (thumbnails), metadata extraction + +### Re-Upload (Bob, Charlie, etc.) +- **Time:** ~1-2 seconds +- **Bandwidth:** ~2-5KB (NOSTR event query + metadata retrieval) +- **Operations:** Hash calculation, NOSTR query, ipfs get (background pin) +- **Savings:** ~99.9% time reduction, ~99.99% bandwidth reduction + +### Network Effect +- Each re-uploader becomes a new IPFS node hosting the content +- Content becomes more resilient and faster to retrieve +- No redundant storage or bandwidth waste +- Original creator is always credited + +## Example Use Cases + +### Content Creator Protection + +Alice creates an image and uploads it. Bob downloads and re-uploads it. The provenance chain shows Alice as the original creator, protecting her copyright. Bob's upload is instant because it reuses Alice's CID. + +### Viral Content Tracking + +A meme spreads through the network. The upload chain shows the complete distribution path: Alice → Bob → Charlie → Dave → ... Each person's upload is instant, and they all help distribute the original content. + +### File Discovery + +Users can find all instances of a file by searching for its hash, discovering that multiple users are hosting it on IPFS, improving availability. + +### Copyright Verification + +A lawyer can prove original authorship by examining the provenance chain and verifying signatures. The original timestamp is preserved in the first NIP-94 event. + +### Bandwidth Optimization + +A university wants to share a large course video. The first upload takes 5 minutes. All subsequent "uploads" by students are instant and reuse the same CID, saving massive bandwidth. + +## Compatibility + +This extension is backward compatible with NIP-94: +- Standard NIP-94 clients can ignore provenance tags +- The file metadata (url, hash, mime type) remains unchanged +- Additional tags don't interfere with existing functionality + diff --git a/96-ipfs-extension.md b/96-ipfs-extension.md new file mode 100644 index 0000000000..9655e00c45 --- /dev/null +++ b/96-ipfs-extension.md @@ -0,0 +1,331 @@ +NIP-96 IPFS Extension +====================== + +IPFS-Optimized File Storage with Twin-Key Authentication +--------------------------------------------------------- + +`draft` `extension` `optional` + +This document describes an extension to [NIP-96](96.md) that integrates **IPFS content addressing**, **provenance tracking** (see [NIP-94 Provenance Extension](94-provenance-extension.md)), and **Twin-Key authentication** (see [NIP-101](101.md)) for decentralized file storage. + +## Overview + +This extension enhances NIP-96 by: + +1. **Native IPFS support** - Files are stored directly on IPFS with content-addressed CIDs +2. **Provenance tracking** - Automatic deduplication and upload chain tracking +3. **Twin-Key integration** - UPlanet geographic identity authentication +4. **Metadata centralization** - Single `info.json` file containing all file metadata +5. **Hybrid API** - REST API with NIP-98 authentication for file operations + +## Motivation + +Standard NIP-96 servers: +- ❌ Store files with arbitrary URLs (no content addressing) +- ❌ Cannot detect duplicate uploads +- ❌ Lack geographic context for content +- ❌ Require external metadata management + +This extension solves these problems by: +- ✅ Using IPFS CIDs as canonical file identifiers +- ✅ Detecting duplicates via SHA256 hash matching +- ✅ Linking files to geographic UMAPs via Twin-Keys +- ✅ Centralizing metadata in `info.json` stored on IPFS + +## Implementation + +### Server Adaptation (NIP-96 Compatible) + +Servers implementing this extension MUST provide the standard NIP-96 discovery endpoint: + +```jsonc +// GET /.well-known/nostr/nip96.json +{ + "api_url": "https://u.copylaradio.com/upload2ipfs", + "download_url": "https://ipfs.copylaradio.com", + "supported_nips": [96, 98, 94], + "tos_url": "https://copylaradio.com/terms", + "content_types": ["image/*", "video/*", "audio/*", "application/pdf"], + "plans": { + "free": { + "name": "Free IPFS Storage", + "is_nip98_required": true, + "max_byte_size": 104857600, + "file_expiration": [0, 0], + "media_transformations": { + "image": ["thumbnail"], + "video": ["thumbnail", "gif_animation"] + } + } + }, + "extensions": { + "ipfs": true, + "provenance": true, + "twin_key": true, + "info_json": true + } +} +``` + +### Upload Endpoint + +`POST /upload2ipfs` + +**Authentication:** [NIP-98](98.md) Authorization header (REQUIRED) + +**Request:** +- `Content-Type: multipart/form-data` +- File in `file` field + +**Response (Extended NIP-96):** +```jsonc +{ + "status": "success", + "message": "File uploaded successfully", + "new_cid": "QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG", + "info": "QmInfoCID123...", + "thumbnail_ipfs": "QmThumbCID456...", + "gifanim_ipfs": "QmGifCID789...", + "duration": 120.5, + "dimensions": "1920x1080", + "file_type": "video/mp4", + "file_hash": "sha256_hash_of_file", + "provenance": { + "original_event_id": "abc123...", + "original_author": "63fe6318dc...", + "upload_chain": "alice_hex,bob_hex", + "is_reupload": true + }, + "nip94_event": { + "tags": [ + ["url", "/ipfs/QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG/video.mp4"], + ["x", "sha256_hash_of_file"], + ["ox", "sha256_hash_of_file"], + ["m", "video/mp4"], + ["size", "52428800"], + ["dim", "1920x1080"], + ["info", "QmInfoCID123..."], + ["thumbnail_ipfs", "QmThumbCID456..."], + ["gifanim_ipfs", "QmGifCID789..."], + ["e", "original_event_id", "", "mention"], + ["p", "original_author"], + ["upload_chain", "alice_hex,bob_hex"] + ] + } +} +``` + +### info.json Structure + +All file metadata is centralized in a single JSON file stored on IPFS: + +```jsonc +{ + "file": { + "name": "video.mp4", + "type": "video/mp4", + "size": 52428800, + "hash": "sha256_hash_of_file", + "ipfs": { + "cid": "QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG", + "path": "/ipfs/QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG/video.mp4" + } + }, + "media": { + "duration": 120.5, + "dimensions": "1920x1080", + "video_codecs": "h264", + "audio_codecs": "aac", + "thumbnail_ipfs": "QmThumbCID456...", + "gifanim_ipfs": "QmGifCID789..." + }, + "metadata": { + "description": "User-provided description", + "type": "video", + "uploaded": "2025-11-03T12:00:00Z" + }, + "provenance": { + "original_event_id": "abc123...", + "original_author": "63fe6318dc...", + "upload_chain": "alice_hex,bob_hex", + "is_reupload": true + } +} +``` + +### Provenance and Deduplication + +The server MUST: +1. Calculate file SHA256 hash **before** IPFS upload +2. Query local Nostr relay for existing NIP-94 events with matching hash +3. If found: + - Reuse existing CID (skip `ipfs add`) + - Download and pin existing file locally via `ipfs get` + - Reuse existing `info.json` metadata + - Update `upload_chain` with current uploader + - Add provenance tags (`e`, `p`, `upload_chain`) +4. If not found: + - Upload file to IPFS normally + - Generate thumbnails and metadata + - Create new `info.json` + +**Benefits:** +- ⚡ Re-uploads are instant (~1-2 seconds instead of minutes) +- 💾 Zero redundant storage +- 🌐 Each re-uploader helps distribute content via IPFS pinning +- 📜 Complete attribution and provenance chain + +See [NIP-94 Provenance Extension](94-provenance-extension.md) for detailed implementation. + +### Twin-Key Authentication + +When users authenticate via NIP-98, the server MAY: +1. Extract the `pubkey` from the NIP-98 event +2. Resolve the DID Document (kind 30800) from the local relay +3. Associate uploaded files with the user's geographic UMAP +4. Tag files with geographic metadata (`latitude`, `longitude`, `g`) + +Example: +```jsonc +{ + "kind": 1063, + "pubkey": "user_hex", + "tags": [ + ["url", "/ipfs/QmCID/file.mp4"], + ["x", "sha256_hash"], + ["did", "did:nostr:user_hex"], + ["latitude", "43.60"], + ["longitude", "1.44"], + ["g", "spey6"], + ["application", "UPlanet"] + ] +} +``` + +This enables: +- 🗺️ Geographic discovery of content +- 🏘️ Localized media feeds per UMAP +- 🔗 Integration with UPlanet identity system + +See [NIP-101](101.md) for Twin-Key system details. + +## API Extensions + +### File Upload with Geographic Context + +`POST /api/fileupload` + +**Authentication:** NIP-42 or form parameter `npub` + +**Request:** +``` +Content-Type: multipart/form-data + +file: [binary] +npub: npub1... +latitude: 43.60 (optional) +longitude: 1.44 (optional) +``` + +**Response:** Same as `/upload2ipfs` plus: +```jsonc +{ + "auth_verified": true, + "umap": "43.60,1.44", + "did": "did:nostr:user_hex" +} +``` + +### Video Publishing with NIP-71 Integration + +`POST /webcam` + +Publishes video to IPFS and creates NIP-71 video event. + +**Request:** +``` +Content-Type: application/x-www-form-urlencoded + +player: npub1... +ipfs_cid: QmCID +thumbnail_ipfs: QmThumbCID +gifanim_ipfs: QmGifCID +info_cid: QmInfoCID +title: Video Title +description: Video Description +publish_nostr: true +latitude: 43.60 +longitude: 1.44 +``` + +**Response:** +```jsonc +{ + "status": "success", + "ipfs_url": "/ipfs/QmCID/video.mp4", + "nostr_event_id": "event_id_hex", + "umap": "43.60,1.44" +} +``` + +This creates a NIP-71 video event with: +- `imeta` tags with thumbnail and GIF +- Geographic tags (`latitude`, `longitude`, `g`) +- UPlanet application tag +- Provenance tags (if file was re-uploaded) + +## Client Behavior + +### Uploading Files + +Clients SHOULD: +1. Sign NIP-98 event with user's keypair +2. Include `Authorization: Nostr ` header +3. Send file via `multipart/form-data` +4. Optionally include geographic coordinates +5. Handle instant response for duplicate files + +### Displaying Files + +Clients SHOULD: +1. Use `/ipfs/CID` URLs for content addressing +2. Display provenance information (upload chain) +3. Show geographic context if available +4. Prefer animated GIF thumbnails when available +5. Load metadata from `info.json` if needed + +## Compatibility + +This extension is fully compatible with: +- ✅ [NIP-96](96.md) - HTTP File Storage Integration +- ✅ [NIP-98](98.md) - HTTP Auth +- ✅ [NIP-94](94.md) - File Metadata +- ✅ [NIP-71](71.md) - Video Events (via extension) +- ✅ [NIP-101](101.md) - UPlanet Identity + +Standard NIP-96 clients can use this server by ignoring IPFS-specific fields. + +## Security Considerations + +- All files are content-addressed (tamper-proof) +- Provenance chain is cryptographically signed +- NIP-98 authentication prevents unauthorized uploads +- IPFS pinning is optional (users can choose distribution) +- Geographic tags may reveal user location (use with caution) + +## Reference Implementation + +- **Server:** `UPassport/54321.py` (FastAPI) +- **Upload Script:** `UPassport/upload2ipfs.sh` (Bash) +- **Frontend:** `UPassport/templates/webcam.html`, `UPlanet/earth/common.js` +- **Repository:** [github.com/papiche/UPassport](https://github.com/papiche/UPassport) + +## License + +This specification is released under **AGPL-3.0**. + +## Authors + +- **papiche** - [github.com/papiche](https://github.com/papiche) +- **CopyLaRadio SCIC** - Cooperative implementation + diff --git a/A0-encryption-extension.md b/A0-encryption-extension.md new file mode 100644 index 0000000000..d32b7e3825 --- /dev/null +++ b/A0-encryption-extension.md @@ -0,0 +1,236 @@ +NIP-A0 Encryption Extension +============================ + +Voice Message Encryption +------------------------ + +**Status:** Draft +**Depends on:** [NIP-A0](A0.md), [NIP-44](44.md) + +This extension adds end-to-end encryption support to NIP-A0 voice messages, allowing users to send private voice messages that only the intended recipient(s) can decrypt. + +## Motivation + +While NIP-A0 defines voice messages (kinds 1222 and 1244), it does not specify encryption. This extension adds optional encryption using NIP-44 (recommended) or NIP-04 (legacy), allowing voice messages to be: + +- **Public**: Accessible to anyone (default behavior, compatible with base NIP-A0) +- **Encrypted**: Only decryptable by specific recipient(s) using their private keys + +## Specification + +### Encryption Modes + +Voice messages can be published in two modes: + +1. **Public Mode** (default): + - `content` field contains a direct URL to the audio file (as per NIP-A0) + - Audio file is stored publicly on IPFS or other storage + - Anyone can access and play the audio + - Compatible with base NIP-A0 specification + +2. **Encrypted Mode**: + - `content` field contains an encrypted payload (NIP-44 format) + - The encrypted payload contains: + - Audio file URL (encrypted) + - Optional metadata (duration, waveform, etc.) + - Audio file itself MAY be encrypted and stored separately, OR + - Audio file MAY be stored publicly but URL is encrypted (metadata hiding) + - Only recipients with the correct private key can decrypt + +### Event Structure + +#### Public Voice Message (NIP-A0 compatible) + +```json +{ + "kind": 1222, + "content": "https://ipfs.io/ipfs/QmXXX.../voice.m4a", + "tags": [ + ["imeta", "url https://ipfs.io/ipfs/QmXXX.../voice.m4a", "duration 45", "waveform ..."] + ] +} +``` + +#### Encrypted Voice Message + +```json +{ + "kind": 1222, + "content": "nip44encryptedpayload...", + "tags": [ + ["p", ""], + ["p", ""], + ["encrypted", "true"], + ["encryption", "nip44"], + ["imeta", "duration 45"], // Public metadata (optional) + ["expiration", "1752600000"] // NIP-40: Optional expiration timestamp + ] +} +``` + +### Encryption Process + +1. **Prepare audio metadata**: + - Audio file URL (IPFS CID or other storage) + - Duration (seconds) + - Waveform data (optional) + - Geographic location (optional, if geolocalized) + +2. **Create plaintext JSON**: + ```json + { + "url": "https://ipfs.io/ipfs/QmXXX.../voice.m4a", + "duration": 45, + "waveform": "0 7 35 8 100...", + "latitude": 48.8566, + "longitude": 2.3522 + } + ``` + +3. **Encrypt using NIP-44** (recommended): + - For single recipient: Use `window.nostr.nip44.encrypt(recipient_pubkey, plaintext_json)` + - For multiple recipients: Encrypt separately for each recipient OR use a shared secret approach + - Store encrypted payload in `content` field + +4. **Add tags**: + - `["p", ""]` for each recipient + - `["encrypted", "true"]` to indicate encryption + - `["encryption", "nip44"]` or `["encryption", "nip04"]` to specify method + - Public metadata tags (duration, etc.) MAY be included for preview + - `["expiration", ""]` MAY be included for NIP-40 expiration support + +### Decryption Process + +1. **Check if encrypted**: + - Look for `["encrypted", "true"]` tag + - If not present, treat as public (NIP-A0 compatible) + +2. **Identify encryption method**: + - Check `["encryption", "nip44"]` or `["encryption", "nip04"]` tag + - Default to NIP-44 if not specified + +3. **Decrypt content**: + - Use `window.nostr.nip44.decrypt(sender_pubkey, encrypted_content)` + - Parse decrypted JSON to extract audio URL and metadata + +4. **Load and play audio**: + - Fetch audio from decrypted URL + - Display metadata (duration, waveform, location) + +### Multiple Recipients + +For multiple recipients, two approaches are supported: + +**Approach 1: Separate encryption per recipient** (recommended for small groups): +- Create separate events for each recipient +- Each event has `content` encrypted with that recipient's public key +- All events reference the same audio file URL (may be public or encrypted separately) + +**Approach 2: Shared secret** (for larger groups): +- Encrypt audio URL with a randomly generated symmetric key +- Encrypt the symmetric key separately for each recipient using NIP-44 +- Store encrypted symmetric keys in tags: `["key", ""]` +- Store encrypted audio URL in `content` + +### Geographic Localization + +Encrypted voice messages MAY include geographic coordinates: + +- If public: Use `g` tag with geohash (NIP-A0 compatible) +- If encrypted: Include coordinates in encrypted payload JSON +- Clients SHOULD display location on map only after decryption + +### Audio File Storage + +The audio file itself can be stored in two ways: + +1. **Public storage** (metadata hiding): + - Audio file stored publicly on IPFS + - Only the URL is encrypted + - Provides privacy for metadata but not for audio content + - Useful for: hiding who can access the message + +2. **Encrypted storage** (full E2EE): + - Audio file encrypted before upload + - Encrypted file stored on IPFS + - Both URL and file are encrypted + - Provides full end-to-end encryption + - Recommended for sensitive voice messages + +### Compatibility + +- **Backward compatible**: Public voice messages (without encryption) remain fully compatible with base NIP-A0 +- **Client support**: Clients that don't support encryption will see encrypted content but cannot decrypt +- **Fallback**: Clients SHOULD display a message indicating encryption is not supported + +## Examples + +### Encrypted Voice Message (Single Recipient) + +```json +{ + "kind": 1222, + "content": "nip44encryptedpayloadbase64...", + "created_at": 1752501052, + "pubkey": "sender_pubkey", + "tags": [ + ["p", "recipient_pubkey"], + ["encrypted", "true"], + ["encryption", "nip44"], + ["imeta", "duration 45"], + ["expiration", "1752600000"] // NIP-40: Optional expiration timestamp + ], + "id": "...", + "sig": "..." +} +``` + +### Encrypted Voice Message (Multiple Recipients) + +```json +{ + "kind": 1222, + "content": "nip44encryptedpayloadbase64...", + "created_at": 1752501052, + "pubkey": "sender_pubkey", + "tags": [ + ["p", "recipient1_pubkey"], + ["p", "recipient2_pubkey"], + ["encrypted", "true"], + ["encryption", "nip44"], + ["imeta", "duration 45"], + ["expiration", "1752600000"] // NIP-40: Optional expiration timestamp + ], + "id": "...", + "sig": "..." +} +``` + +### Decrypted Content (example) + +```json +{ + "url": "https://ipfs.io/ipfs/QmXXX.../voice_encrypted.m4a", + "duration": 45, + "waveform": "0 7 35 8 100 100 49 8...", + "latitude": 48.8566, + "longitude": 2.3522, + "file_encrypted": true +} +``` + +## Security Considerations + +1. **NIP-44 is recommended** over NIP-04 for better security +2. **Audio file encryption**: For maximum privacy, encrypt the audio file before upload +3. **Metadata leakage**: Public tags (duration, etc.) may leak information +4. **Relay trust**: Relays can see event metadata but not decrypted content +5. **Forward secrecy**: Not provided by this extension (see NIP-44 limitations) + +## Implementation Notes + +- Clients SHOULD use NIP-44 (`window.nostr.nip44.encrypt/decrypt`) when available +- Clients MAY fall back to NIP-04 for backward compatibility +- Audio files SHOULD be limited to 60 seconds as per NIP-A0 +- Maximum file size: 10MB (recommended for encrypted voice messages) + diff --git a/README.md b/README.md index ccae3aa006..8161bff1cd 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos - [NIP-56: Reporting](56.md) - [NIP-57: Lightning Zaps](57.md) - [NIP-58: Badges](58.md) + - [NIP-58 Oracle Badges Extension](58-oracle-badges-extension.md) - UPlanet Oracle System integration - [NIP-59: Gift Wrap](59.md) - [NIP-60: Cashu Wallet](60.md) - [NIP-61: Nutzaps](61.md) @@ -102,6 +103,7 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos - [NIP-96: HTTP File Storage Integration](96.md) --- **unrecommended**: replaced by blossom APIs - [NIP-98: HTTP Auth](98.md) - [NIP-99: Classified Listings](99.md) +- [NIP-101: UPlanet - Decentralized Identity & Geographic Coordination](101.md) - [NIP-A0: Voice Messages](A0.md) - [NIP-B0: Web Bookmarks](B0.md) - [NIP-B7: Blossom](B7.md) @@ -120,7 +122,7 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos | `5` | Event Deletion Request | [09](09.md) | | `6` | Repost | [18](18.md) | | `7` | Reaction | [25](25.md) | -| `8` | Badge Award | [58](58.md) | +| `8` | Badge Award | [58](58.md), [58-oracle](58-oracle-badges-extension.md) | | `9` | Chat Message | [C7](C7.md) | | `10` | Group Chat Threaded Reply | 29 (deprecated) | | `11` | Thread | [7D](7D.md) | @@ -235,8 +237,8 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos | `30004` | Curation sets | [51](51.md) | | `30005` | Video sets | [51](51.md) | | `30007` | Kind mute sets | [51](51.md) | -| `30008` | Profile Badges | [58](58.md) | -| `30009` | Badge Definition | [58](58.md) | +| `30008` | Profile Badges | [58](58.md), [58-oracle](58-oracle-badges-extension.md) | +| `30009` | Badge Definition | [58](58.md), [58-oracle](58-oracle-badges-extension.md) | | `30015` | Interest sets | [51](51.md) | | `30017` | Create or update a stall | [15](15.md) | | `30018` | Create or update a product | [15](15.md) | @@ -252,16 +254,21 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos | `30166` | Relay Discovery | [66](66.md) | | `30267` | App curation sets | [51](51.md) | | `30311` | Live Event | [53](53.md) | -| `30312` | Interactive Room | [53](53.md) | -| `30313` | Conference Event | [53](53.md) | +| `30312` | Interactive Room / ORE Meeting Space | [53](53.md), [101](101.md) | +| `30313` | Conference Event / ORE Verification | [53](53.md), [101](101.md) | | `30315` | User Statuses | [38](38.md) | | `30388` | Slide Set | [Corny Chat][cornychat-slideset] | | `30402` | Classified Listing | [99](99.md) | | `30403` | Draft Classified Listing | [99](99.md) | +| `30500` | Permit Definition | [101](101.md) | +| `30501` | Permit Request | [101](101.md) | +| `30502` | Permit Attestation | [101](101.md) | +| `30503` | Permit Credential | [101](101.md) | | `30617` | Repository announcements | [34](34.md) | | `30618` | Repository state announcements | [34](34.md) | | `30818` | Wiki article | [54](54.md) | | `30819` | Redirects | [54](54.md) | +| `30800` | DID Document | [101](101.md) | | `31234` | Draft Event | [37](37.md) | | `31388` | Link Set | [Corny Chat][cornychat-linkset] | | `31890` | Feed | [NUD: Custom Feeds][NUD: Custom Feeds] | diff --git a/UPLANET_EXTENSIONS.md b/UPLANET_EXTENSIONS.md new file mode 100644 index 0000000000..8082888a9b --- /dev/null +++ b/UPLANET_EXTENSIONS.md @@ -0,0 +1,655 @@ +# UPlanet Protocol Extensions + +Official Nostr Protocol Extensions for the UPlanet/Astroport.ONE Ecosystem + +--- + +**Status:** 🟢 Production (since 2024) + +**Version:** 2.0.0 + +**Last Updated:** November 3, 2025 + +--- + +## 📖 Quick Navigation + +### Core Systems +- **Identity & DIDs:** [NIP-101](101.md) + [42-twin-key-extension.md](42-twin-key-extension.md) +- **Oracle Permits:** [42-oracle-permits-extension.md](42-oracle-permits-extension.md) +- **Oracle Badges:** [58-oracle-badges-extension.md](58-oracle-badges-extension.md) - Gamification NIP-58 +- **Relay Sync:** [101-n2-constellation-sync-extension.md](101-n2-constellation-sync-extension.md) + +### File & Media Management +- **File Provenance:** [94-provenance-extension.md](94-provenance-extension.md) +- **IPFS Storage:** [96-ipfs-extension.md](96-ipfs-extension.md) +- **Video Events:** [71-extension.md](71-extension.md) + +### Geographic & Social +- **UMAP Chat Rooms:** [28-umap-extension.md](28-umap-extension.md) + +### Documentation +- **Full Documentation:** [DID_IMPLEMENTATION.md](../Astroport.ONE/DID_IMPLEMENTATION.md) +- **ORE System:** [ORE_SYSTEM.md](../Astroport.ONE/docs/ORE_SYSTEM.md) +- **Oracle System:** [ORACLE_SYSTEM.md](../Astroport.ONE/docs/ORACLE_SYSTEM.md) +- **Economy:** [ZEN.ECONOMY.readme.md](../Astroport.ONE/RUNTIME/ZEN.ECONOMY.readme.md) + +--- + +## 📚 Complete Documentation + +This repository contains **9 official extensions** to the Nostr protocol for the UPlanet/Astroport.ONE ecosystem. + +### 🎯 Overview + +UPlanet extends Nostr with: +- 🗺️ **Geographic Identity** - Twin-Key system for location-based coordination +- 🔐 **Decentralized Identity (DID)** - W3C-compliant identities on Nostr +- 📁 **IPFS Integration** - Content-addressed file storage +- 🌐 **Provenance Tracking** - File attribution and deduplication +- 🎫 **Peer-Validated Credentials** - Oracle system for competence verification +- 🏅 **Badge Gamification** - NIP-58 badges for visual competence representation +- 🔄 **N² Synchronization** - Mesh network of relays for resilience +- 🌱 **Environmental Registry** - ORE (Ecological Real Obligations) system +- 💬 **Geographic Chat Rooms** - UMAP-based location discussion channels + +--- + +## 📋 Extension List + +### 🌟 Core Specification + +- **[NIP-101](101.md)** - UPlanet: Decentralized Identity & Geographic Coordination + - Hierarchical GeoKeys (UMAP, SECTOR, REGION) + - DID Documents (kind 30800) + - Oracle System (kinds 30500-30503) + - ORE Environmental Registry (kinds 30312-30313) + +- **[NIP-101 N² Constellation Sync Extension](101-n2-constellation-sync-extension.md)** - Astroport Relay Synchronization + - **Extends:** NIP-101 (UPlanet Identity & Geographic Coordination) + - **Architecture:** Hub (1) + Satellites (24) = N² synchronization matrix (600 sync paths) + - **Synchronized kinds:** 21 event types (0,1,3,5,6,7,8,21,22,30008,30009,30023,30024,30312,30313,30500,30501,30502,30503,30800) + - **Innovation:** Automatic peer discovery + bidirectional sync + geographic hierarchical coordination + - **Topology:** Hub-and-satellite with mesh network capabilities + - **Resilience:** 25x event replication across constellation (zero data loss even if 24 relays fail) + +### 🔐 Authentication & Identity Extensions + +- **[NIP-42 Twin-Key Extension](42-twin-key-extension.md)** - Geographic Identity Authentication + - **Extends:** NIP-42 (Authentication to relays) + - **Kind:** 22242 (auth events) + - **Innovation:** Dual authentication (personal keypair + UMAP geographic keypair) + - **New tags:** `did`, `umap`, `g` (geohash), `latitude`, `longitude`, `grid_level` + - **Process:** User signs → UMAP signs → Relay links identities + - **Use case:** Location-based content access + permission control + +- **[NIP-42 Oracle Permits Extension](42-oracle-permits-extension.md)** - Multi-Signature Permit Management + - **Extends:** NIP-42 (Authentication to relays) + - **New kinds:** 30500 (definitions), 30501 (requests), 30502 (attestations), 30503 (credentials) + - **Innovation:** Web of Trust (WoT) multi-signature competence validation + - **Flow:** Define permit → Request → N attestations → Auto-issue W3C VC → Add to DID + - **Examples:** `PERMIT_ORE_V1` (5 sigs), `PERMIT_DRIVER` (12 sigs, WoT insurance mutual) + - **Integration:** Enhanced NIP-42 auth with `permit` and `credential_id` tags + +- **[NIP-58 Oracle Badges Extension](58-oracle-badges-extension.md)** - Gamification of Competence Certification + - **Extends:** NIP-58 (Badges) + - **Kinds:** 30009 (badge definitions), 8 (badge awards), 30008 (profile badges) + - **Innovation:** Automatic badge emission when Oracle credentials (30503) are issued + - **Flow:** Credential issued → Badge definition created → Badge award emitted → Visible in profiles + - **Features:** Visual representation of validated competencies, WoTx2 level badges, ORE achievement badges + - **Integration:** Displayed in `/oracle`, `/wotx2`, `/plantnet` interfaces + +### 📁 File Storage & Media Extensions + +- **[NIP-71 Extension](71-extension.md)** - Video Events with IPFS & Thumbnails + - **Extends:** NIP-71 (Video Events) + - **Kinds:** 21 (normal videos), 22 (short videos < 60s) - **standard NIP-71** + - **New tags:** `info` (info.json CID), `thumbnail_ipfs`, `gifanim_ipfs` + - **Innovation:** Animated GIF previews + centralized `info.json` metadata on IPFS + - **Compatibility:** Maintains NIP-71 `imeta` tag structure + adds IPFS-specific tags + +- **[NIP-94 Provenance Extension](94-provenance-extension.md)** - File Provenance & Upload Chain + - **Extends:** NIP-94 (File Metadata) + - **Kind:** 1063 (file metadata events) + - **New tags:** `upload_chain` (comma-separated pubkeys), `e`/`p` for provenance + - **Innovation:** SHA256 deduplication BEFORE IPFS upload (99.9% faster re-uploads) + - **Process:** Calculate hash → Query NOSTR (kind 1063 + tag `x`) → Reuse CID via `ipfs get` → Pin locally + - **Network effect:** Each re-upload strengthens IPFS distribution + +- **[NIP-96 IPFS Extension](96-ipfs-extension.md)** - IPFS-Optimized File Storage + - **Extends:** NIP-96 (HTTP File Storage Integration) + - **Endpoint:** `POST /upload2ipfs` (NIP-98 auth required) + - **Discovery:** `/.well-known/nostr/nip96.json` with `extensions.ipfs: true` + - **Innovation:** Native IPFS + provenance + Twin-Key authentication + `info.json` + - **Response:** Extended NIP-96 format with `provenance` object + +### 💬 Social & Communication Extensions + +- **[NIP-28 UMAP Extension](28-umap-extension.md)** - Geographic Chat Rooms + - **Extends:** NIP-28 (Public Chat) + - **Kind:** 42 (channel messages) + - **New tags:** `g` (geographic coordinates), references UMAP DID npub in `e` tag + - **Innovation:** Location-based discussion rooms tied to UMAP DIDs (kind 30800) + - **Channel ID:** Uses UMAP DID's `npub` or fallback to `UMAP__` + - **Use case:** Geographic coordination, ORE verification discussions, local community chat + - **Integration:** Works with ORE System (kind 30312/30313) for environmental compliance + +--- + +## 🏗️ System Architecture + +``` +┌─────────────────────────────────────────────────────────────┐ +│ UPlanet Architecture │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ NIP-101 │ │ NIP-42 │ │ NIP-96 │ │ +│ │ Identity │─▶│ Twin-Key │◀─│ IPFS │ │ +│ │ + GeoKeys │ │ + Oracle │ │ Storage │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ Nostr Relay (Astroport + strfry) │ │ +│ │ - Geographic event routing (UMAP/SECTOR) │ │ +│ │ - DID resolution (kind 30800) │ │ +│ │ - Provenance tracking (NIP-94 extension) │ │ +│ │ - N² constellation sync (18 event kinds) │ │ +│ └──────────────────────────────────────────────────┘ │ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ NIP-71 │ │ NIP-94 │ │ Oracle │ │ +│ │ Video │ │ Provenance │ │ System │ │ +│ │ + GIF │ │ + Chain │ │ (30500-503) │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ IPFS Network │ + │ Content Store │ + └─────────────────┘ +``` + +### Hub-and-Satellite Topology + +``` +Hub Central (1) +├── Coordinates 24 Satellites +├── Manages inter-satellite communication +├── Handles economic flows (Ẑen) +└── Provides global synchronization + +Satellites (24) +├── Local services (MULTIPASS, ZEN Cards) +├── Geographic UMAP management +├── Bidirectional sync with Hub +└── Peer sync with other Satellites +``` + +--- + +## 📊 Implementation Matrix + +| Feature | Standard NIP | UPlanet Extension | Kind(s) | Status | +|---------|-------------|-------------------|---------|--------| +| **Authentication** | NIP-42 | [42-twin-key-extension.md](42-twin-key-extension.md) | 22242 | ✅ Production | +| **Oracle Permits** | NIP-42 | [42-oracle-permits-extension.md](42-oracle-permits-extension.md) | 30500-30503 | ✅ Production | +| **Oracle Badges** | NIP-58 | [58-oracle-badges-extension.md](58-oracle-badges-extension.md) | 30009, 8, 30008 | ✅ Production | +| **File Metadata** | NIP-94 | [94-provenance-extension.md](94-provenance-extension.md) | 1063 | ✅ Production | +| **File Storage** | NIP-96 | [96-ipfs-extension.md](96-ipfs-extension.md) | - (HTTP API) | ✅ Production | +| **Video Events** | NIP-71 | [71-extension.md](71-extension.md) | 21, 22 | ✅ Production | +| **Geographic Chat** | NIP-28 | [28-umap-extension.md](28-umap-extension.md) | 42 | ✅ Production | +| **Identity** | - | [NIP-101](101.md) | 30800 | ✅ Production | +| **Relay Sync** | NIP-101 | [101-n2-constellation-sync-extension.md](101-n2-constellation-sync-extension.md) | 18 kinds | ✅ Production | + +### Event Kind Reference + +| Kind | Name | Extension | Purpose | +|------|------|-----------|---------| +| 0 | Profile | Core | User profile metadata | +| 1 | Text Note | Core | Standard text posts | +| 3 | Contacts | Core | Contact/follow lists | +| 5 | Deletion | Core | Event deletion requests | +| 6 | Repost | Core | Event reposts | +| 7 | Reaction | Core | Like/reaction events | +| 21 | Media | Core | Media attachments | +| 22 | Comment | NIP-22 | Comment threads | +| 42 | Channel Message | NIP-28 + UMAP | Geographic chat room messages | +| 1063 | File Metadata | NIP-94 + Provenance | File metadata with upload chain | +| 22242 | Auth | NIP-42 + Twin-Key | Authentication with UMAP keypair | +| 30023 | Article | NIP-23 | Long-form content | +| 30024 | Draft | NIP-23 | Article drafts | +| 30312 | ORE Space | NIP-101 | Geographic meeting room | +| 30313 | ORE Verification | NIP-101 | Environmental validation | +| 30500 | Permit Definition | Oracle | License type definition | +| 30501 | Permit Request | Oracle | Application for permit | +| 30502 | Permit Attestation | Oracle | Expert signature | +| 30503 | Permit Credential | Oracle | W3C Verifiable Credential | +| 30800 | DID Document | NIP-101 | Decentralized identity | +| 10000 | Analytics | [10000-analytics-extension.md](10000-analytics-extension.md) | User analytics events (decentralized, supports both encrypted and unencrypted via content/tags) | +| 10001 | ~~Encrypted Analytics~~ (Deprecated) | [10001-encrypted-analytics-extension.md](10001-encrypted-analytics-extension.md) | **DEPRECATED**: Now reserved for NIP-51 playlists (pin list). Use kind 10000 for encrypted analytics. | +| 21 | Video (Normal) | NIP-71 + IPFS | Normal videos (landscape, longer) | +| 22 | Video (Short) | NIP-71 + IPFS | Short videos (portrait, < 60s) | + +--- + +## 🚀 Quick Start + +### For Developers + +1. **Read this guide** - Complete overview of UPlanet extensions +2. **Choose an extension** based on your needs +3. **Review reference implementations** (links in each doc) +4. **Test locally** with provided scripts + +### For Users + +1. **Create a MULTIPASS**: `./make_NOSTRCARD.sh email@example.com` +2. **Access the portal**: https://ipfs.copylaradio.com/ipns/copylaradio.com +3. **Use the services**: + - 📹 Nostr Tube (videos) + - 🗺️ UMAP (geolocation) + - 🌱 ORE (environmental obligations) + - 🎫 Oracle (multi-signature permits) + +--- + +## 🎯 Use Cases + +### 1. Geographic Social Network + +Alice publishes from her UMAP neighborhood: +- Twin-Key authentication (NIP-42 extension) +- Publication with geographic tag +- N² synchronization to constellation +- Bob (subscribed to UMAP) sees post instantly + +### 2. Decentralized Video Platform + +Carol uploads a video: +- Upload to IPFS (NIP-96 extension) +- Thumbnails auto-generated (static + GIF) +- Metadata in info.json +- NIP-71 event published +- Dave re-uploads same video → instant (provenance tracking) + +### 3. Competence Certification + +Eve becomes environmental verifier: +- Requests PERMIT_ORE_V1 (kind 30501) +- 5 experts attest her competence (kind 30502) +- Oracle issues VC (kind 30503) +- Badge NIP-58 automatically emitted (kind 30009 + kind 8) +- Credential added to Eve's DID (kind 30800) +- Eve receives 10 Ẑen reward + +### 4. Environmental Obligations + +UMAP with ORE contract: +- UMAP DID created (kind 30800) +- ORE verification room (kind 30312) +- Expert verification (kind 30313) +- Automatic payment 10 Ẑen +- Redistribution to local guardians + +--- + +## 🧪 Testing + +```bash +# Test DIDs +cd Astroport.ONE/tools +./test_did_conformity.sh user@example.com + +# Test Oracle system +./test_permit_system.sh --all + +# Test ORE system +./ore_complete_test.sh +``` + +--- + +## 📚 Additional Documentation + +### Core Systems +- [DID_IMPLEMENTATION.md](../Astroport.ONE/DID_IMPLEMENTATION.md) - Complete identity system +- [ORE_SYSTEM.md](../Astroport.ONE/docs/ORE_SYSTEM.md) - Environmental obligations +- [ORACLE_SYSTEM.md](../Astroport.ONE/docs/ORACLE_SYSTEM.md) - Permit system +- [ZEN.ECONOMY.readme.md](../Astroport.ONE/RUNTIME/ZEN.ECONOMY.readme.md) - Ẑen economy + +### Reference Implementations +- **Backend:** [UPassport/54321.py](../UPassport/54321.py) +- **Scripts:** [Astroport.ONE/tools/](../Astroport.ONE/tools/) +- **Frontend:** [UPlanet/earth/](../UPlanet/earth/) + +--- + +## 💡 Key Innovations + +### Geographic Coordination +- **Hierarchical GeoKeys** (UMAP/SECTOR/REGION) +- **Location-based event routing** +- **Geographic discovery** via `g` tags +- **Constellation topology** for local resilience + +### Identity & Credentials +- **W3C DID** on Nostr (kind 30800) +- **Twin-Key** authentication (personal + UMAP) +- **Verifiable Credentials** (W3C standard) +- **Multi-signature permits** (Web of Trust) + +### File Management +- **Provenance tracking** via SHA256 +- **Upload chain** (attribution & deduplication) +- **IPFS optimization** (99.9% faster re-uploads) +- **Automatic thumbnails** (static + animated GIF) + +### Network Resilience +- **N² synchronization** (25 relays × 24 peers) +- **Hub-and-satellite** topology +- **18 event kinds** auto-synced +- **Zero single point of failure** + +--- + +## 🔑 Key Architecture & Roles + +### Cryptographic Key Hierarchy + +UPlanet implements a **4-tier key system** that maps system roles to cryptographic identities: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ UPlanet Key Architecture │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ NODE (Armateur) → Machine Owner │ +│ ├── Physical infrastructure (Raspberry Pi / PC Gamer) │ +│ ├── Receives PAF (Participation Aux Frais) │ +│ ├── Hardware capital investment │ +│ └── secret.NODE.dunikey │ +│ │ +│ SUDO (Captain) → System Administrator │ +│ ├── Software maintenance & operations │ +│ ├── Receives 2x PAF compensation │ +│ ├── Manages MULTIPASS and ZEN Card users │ +│ └── CAPTAIN wallet │ +│ │ +│ USER (MULTIPASS) → Tenant / Service Consumer │ +│ ├── 1 Ẑ/week rental (uDRIVE 10GB storage) │ +│ ├── NOSTR identity + basic services │ +│ ├── Can earn Ẑ through content creation │ +│ └── CAPTAIN.MULTIPASS wallet │ +│ │ +│ OWNER (ZEN Card) → Cooperative Member │ +│ ├── 50 Ẑ/year ownership (NextCloud 128GB) │ +│ ├── Cooperative governance rights │ +│ ├── Share in 3x1/3 profit distribution │ +│ └── CAPTAIN.ZENCARD wallet │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Role Relationships + +**Armateur (NODE)** ↔ **Captain (SUDO)** +- Armateur provides hardware infrastructure +- Captain operates and maintains the system +- Split compensation: 1x PAF (Armateur) + 2x PAF (Captain) + +**Captain (SUDO)** ↔ **Users (MULTIPASS/ZEN Card)** +- Captain hosts user services +- Collects weekly rent from users +- Provides storage, identity, and connectivity + +**Cooperative Model** +- Users pay rent (1-5 Ẑ/week) +- Infrastructure costs covered (3x PAF) +- Surplus distributed via 3x1/3 rule: + - 33.33% → TREASURY (operations) + - 33.33% → RND (research & development) + - 33.34% → ASSETS (ecological investments) + +--- + +## 💰 Ẑen Economy + +### Economic Model + +The **Ẑen** (Ẑ) is UPlanet's internal unit of account, backed by the Ğ1 blockchain: + +**Exchange Rate:** +- **UPlanet ORIGIN** (testnet): `1 Ẑ = 0.1 Ğ1` +- **UPlanet Ẑen** (production): `1 Ẑ = 1€` + +### Revenue Streams + +#### MULTIPASS (Digital Studio) +``` +Rent: 1 Ẑ/week HT + 0.2 Ẑ TVA +├── Services: uDRIVE 10GB + NOSTR identity +├── Script: NOSTRCARD.refresh.sh +└── Capacity: 250 per Raspberry Pi station +``` + +#### ZEN Card (Digital Apartment + Ownership) +``` +Ownership: 50 Ẑ/year (one-time social shares purchase) +├── Services: NextCloud 128GB + premium features +├── Rights: Voting + profit sharing +├── Script: PLAYER.refresh.sh +└── Capacity: 24 per station +``` + +### Automated Scripts + +| Script | Function | Frequency | +|--------|----------|-----------| +| `UPLANET.init.sh` | Initialize all wallets | Once | +| `ZEN.ECONOMY.sh` | PAF payment + 4-week burn + capital contribution | Weekly | +| `ZEN.COOPERATIVE.3x1-3.sh` | Surplus calculation & 3x1/3 allocation | Weekly | +| `NOSTRCARD.refresh.sh` | Collect MULTIPASS rent (1Ẑ HT + 0.2Ẑ TVA) | Weekly | +| `PLAYER.refresh.sh` | Collect ZEN Card rent | Weekly | +| `UPLANET.official.sh` | Official Ẑen emission | On-demand | + +### Economic Simulators + +**Live Simulators:** +- [Constellation Simulator](https://ipfs.copylaradio.com/ipns/copylaradio.com/economy.Constellation.html) - Full cooperative model +- [Basic Economy](https://ipfs.copylaradio.com/ipns/copylaradio.com/economy.html) - Simple financial flows + +**Features:** +- Real-time economic projections +- Break-even analysis +- Cooperative profit distribution +- Ecological impact calculation (forest acquisition) +- Infrastructure capacity planning + +--- + +## 🤖 AI Integration + +### Made In Zen AI System + +UPlanet integrates decentralized AI services for cooperative members: + +**AI Services:** +- **Personal AI Assistants** - Private, user-controlled AI instances +- **Content Generation** - Text, image, and media creation tools +- **Community Moderation** - AI-assisted content curation +- **Knowledge Management** - Semantic search and organization + +**Architecture:** +- **Decentralized Processing** - AI runs on member nodes +- **Privacy-First** - User data never leaves their control +- **Ğ1-Based Payments** - AI services paid in Ẑen tokens +- **Cooperative Training** - Community-contributed AI improvements + +**Access Points:** +- MULTIPASS users: Basic AI services included +- ZEN Card owners: Premium AI features + priority access +- Captains: AI tools for system administration + +**Integration:** +- NOSTR events for AI requests/responses +- IPFS storage for AI-generated content +- DID-based authentication for AI services +- Oracle system for AI service validation + +--- + +## 🏗️ Anarchitecture Philosophy + +### Decentralized Sovereignty + +UPlanet implements **Anarchitecture** - a philosophy of decentralized, self-sovereign systems: + +**Core Principles:** +1. **Three-Tier Trust** (vs blind trust in admins) + - **User:** Keeps master key sovereign + - **Relay (Dragon):** Service provider with limited session key + - **Ğ1 Web of Trust:** Distributed human identity certification + +2. **Nation-States of Mind** + - Create chosen collectives based on trust (Ğ1) and shared values + - Digital sovereign territories with group-defined rules + - Ẑen as indicator of group health and value creation + +3. **Cognitive Dystopia Antidote** + - Escape centralized algorithm control + - Build trust networks (N1: friends, N2: friends of friends) + - Curated relays instead of global noise + +### Economic Philosophy + +**Digital-to-Physical Bridge:** +- Online activity generates Ẑen value +- Cooperative profits → ecological regeneration +- Net surplus → forest/farmland acquisition +- Social shares = co-ownership of physical commons + +**Cooperative Model (3x1/3):** +- Not profit-driven but commons-oriented +- Transparent on-chain accounting +- Standardized infrastructure contributions +- Community-validated governance + +### Entry Paths + +| Path | Target | Entry Point | Ẑen Rate | Status | +|------|--------|-------------|----------|--------| +| **🧭 Explorer** | Curious users | [qo-op.com](https://qo-op.com) | 1 Ẑ = 0.1 Ğ1 | Testnet | +| **🛠️ Builder** | Ğ1 members | [OpenCollective](https://opencollective.com/uplanet-zero) | 1 Ẑ = 1€ | Production | + +**Explorer (UPlanet ORIGIN):** +- Email signup in 1 minute +- Test services (AI, NOSTR, storage) +- Discover ecosystem potential +- Internal sandbox economy + +**Builder (UPlanet Ẑen):** +- Join G1FabLab community +- Purchase cooperative shares +- Seal pact with certified Ğ1 account +- Become active SCIC member +- Real cooperative economy + +--- + +## 🤝 Contributing + +Contributions are welcome! + +1. Fork the repository +2. Create a feature branch +3. Test locally +4. Submit a pull request with documentation + +--- + +## 📜 License + +All UPlanet specifications and implementations are under **AGPL-3.0**. + +--- + +## 👥 Community + +- **Matrix:** #uplanet:matrix.org +- **Forum:** https://forum.copylaradio.com +- **GitHub:** [Astroport.ONE Organization](https://github.com/papiche) +- **Email:** contact@copylaradio.com + +### Join the Ecosystem + +**For Explorers (Testnet):** +- Portal: https://qo-op.com +- Demo: https://ipfs.copylaradio.com/ipns/copylaradio.com +- Network Visualization: https://ipfs.copylaradio.com/ipns/copylaradio.com/bang.html + +**For Builders (Production):** +- Cooperative: https://opencollective.com/uplanet-zero +- G1FabLab: https://opencollective.com/monnaie-libre +- SCIC Statutes: https://pad.p2p.legal/s/CopyLaRadio# + +**Economic Tools:** +- Constellation Simulator: https://ipfs.copylaradio.com/ipns/copylaradio.com/economy.Constellation.html +- Basic Economy: https://ipfs.copylaradio.com/ipns/copylaradio.com/economy.html + +**Technical Resources:** +- Help Guide: https://pad.p2p.legal/s/UPlanet_Enter_Help +- Raspberry Pi Guide: https://pad.p2p.legal/s/RaspberryPi# +- Trust Model: https://www.copylaradio.com/blog/blog-1/post/relation-de-confiance-decentralisee-a-3-tiers-avec-la-g1-149 + +--- + +## ✍️ Authors + +**Lead Developers:** +- [papiche](https://github.com/papiche) - UPlanet & Astroport.ONE architecture +- CopyLaRadio SCIC - Cooperative governance & implementation + +**Contributors:** +- Astroport.ONE community +- Ğ1 libre currency community +- NOSTR protocol community + +--- + +## 🌟 Why UPlanet Extensions? + +UPlanet extends Nostr to create a **complete decentralized society infrastructure**: + +### Digital Sovereignty +- **Own your identity** (DID with SSSS 3/2 secret sharing) +- **Control your data** (IPFS + personal storage) +- **Verify your competencies** (W3C Verifiable Credentials) + +### Geographic Coordination +- **Local communities** (UMAP neighborhoods) +- **Regional organization** (SECTOR/REGION hierarchy) +- **Global network** (N² constellation sync) + +### Economic Integration +- **Ẑen currency** (Ğ1 blockchain based) +- **Cooperative model** (3x1/3 profit sharing) +- **Environmental rewards** (ORE system) + +### Technical Excellence +- **W3C standards** (DID Core v1.1, VC Data Model) +- **IPFS integration** (content addressing) +- **Nostr native** (events, relays, protocol) +- **Production ready** (since 2024) + +--- + +**Version:** 2.0.0 +**Last Updated:** November 3, 2025 +**Status:** 🟢 Production + +**🌍 Building a decentralized future, one extension at a time.**