This is the full developer documentation for VisionFusen API
# VisionFusen Docs
> Universal Content Signing für das neue Internet
## Was ist VisionFusen?
[Abschnitt betitelt „Was ist VisionFusen?“](#was-ist-visionfusen)
**Normaler Web-Content rein. Signierter, indexer-ready Output raus.**
Die Brücke zwischen Web2-Publishing und Web3-Ownership. Deine Inhalte werden kryptografisch signiert, für LLMs optimiert und dezentral verteilt.
Universal Signing
Bilder, PDFs, Markdown, Text — alles was einen Hash hat, kann signiert werden. Unveränderlicher Nachweis der Urheberschaft auf Nostr.
LLM-optimiert
Schema.org, JSON-LD, llms.txt — deine Seiten tauchen als Quelle in KI-Antworten auf.
Multi-Distribution
Ein Content, mehrere Kanäle: Nostr Relays + klassisches Web. Edge-delivered.
Monetarisierung
Drei Layer: Web Paywall (LNbits), Value-for-Value (Zaps), L402 API (Agent-to-Agent).
## Unterstützte Formate
[Abschnitt betitelt „Unterstützte Formate“](#unterstützte-formate)
| Typ | Formate | Metadaten-Quelle |
| ------------- | --------------------------------- | ------------------- |
| **Bilder** | WebP, PNG, JPEG, GIF | XMP/EXIF |
| **Dokumente** | PDF | PDF-Info Dictionary |
| **Text** | Markdown (.md), Plain Text (.txt) | YAML Frontmatter |
## Die Pipeline
[Abschnitt betitelt „Die Pipeline“](#die-pipeline)
```plaintext
Datei → Typ erkennen → Metadaten → Signatur → Distribution
MIME-Type XMP/PDF/ NIP-94 Relays + CDN
Frontmatter + Static HTML
```
## Status
[Abschnitt betitelt „Status“](#status)
| Component | Status |
| ----------------------- | ------------------------------------------ |
| **Infrastruktur** | ✅ Live — Relay, nsecBunker, LNbits, NIP-05 |
| **Universal Sign Tool** | ✅ Live — Bilder, PDF, Markdown, Text |
| **Static Event Pages** | ✅ Live — LLM-optimierte HTML auf CDN |
| **Content API v2.1** | ✅ Live — Universal Signing |
| **Lightning** | 🔨 Building — L402, Token System |
| **Private Signing** | 📋 Planned — Nur Hash, ZIP-Beweis |
| **Agent API** | 📋 Planned — Machine-to-Machine |
# Hash & Signatur
> Kryptografische Grundlagen von VisionFusen
VisionFusen nutzt zwei kryptografische Konzepte.
## SHA-256 Hash
[Abschnitt betitelt „SHA-256 Hash“](#sha-256-hash)
Ein “digitaler Fingerabdruck” einer Datei:
```plaintext
Eingabe: Bild (beliebige Größe)
↓
SHA-256
↓
Ausgabe: cd4efae6a87d2c4b8f9e3a7b1c5d2e4f6a8b0c3d5e7f9a1b3c5d7e9f1a3b5c7d
(immer 64 Zeichen)
```
### Eigenschaften
[Abschnitt betitelt „Eigenschaften“](#eigenschaften)
| Eigenschaft | Bedeutung |
| ------------------- | --------------------------------------------- |
| **Deterministisch** | Gleiche Eingabe → gleicher Hash |
| **Einweg** | Aus Hash kann Bild nicht rekonstruiert werden |
| **Lawineneffekt** | Kleinste Änderung → komplett anderer Hash |
## Digitale Signatur
[Abschnitt betitelt „Digitale Signatur“](#digitale-signatur)
Nostr nutzt **Schnorr-Signaturen**:
```plaintext
Private Key (geheim) Public Key (öffentlich)
nsec1... → npub1...
↓ ↓
Zum Signieren Zur Verifikation
```
### Signatur-Prozess
[Abschnitt betitelt „Signatur-Prozess“](#signatur-prozess)
1. Event erstellen (ohne sig)
2. Event ID berechnen = SHA-256 von Event-Daten
3. Event ID signieren mit Private Key
4. Signatur an Event anhängen
## In VisionFusen
[Abschnitt betitelt „In VisionFusen“](#in-visionfusen)
Das **Nostr Event** wird signiert, nicht das Bild direkt:
```json
{
"kind": 1063,
"tags": [["x", "cd4efae6..."]], // ← Hash des Bildes
"sig": "def456..." // ← Signatur des Events
}
```
# NIP-94 erklärt
> Was ist NIP-94 und wie funktioniert es?
**NIP-94** ist ein Nostr Improvement Proposal für Datei-Metadaten.
## Event Struktur
[Abschnitt betitelt „Event Struktur“](#event-struktur)
Ein NIP-94 Event hat **Kind 1063**:
```json
{
"kind": 1063,
"content": "Beschreibung des Bildes",
"tags": [
["url", "https://cdn.example.com/bild.webp"],
["m", "image/webp"],
["x", "cd4efae6..."],
["size", "245760"],
["dim", "1920x1080"],
["title", "Sonnenuntergang"],
["author", "Steven Noack"]
],
"pubkey": "abc123...",
"created_at": 1703001234,
"sig": "def456..."
}
```
## Pflicht-Tags
[Abschnitt betitelt „Pflicht-Tags“](#pflicht-tags)
| Tag | Beschreibung |
| ----- | ------------- |
| `url` | URL zur Datei |
| `m` | MIME Type |
| `x` | SHA-256 Hash |
## Optionale Tags
[Abschnitt betitelt „Optionale Tags“](#optionale-tags)
| Tag | Beschreibung |
| -------- | ------------------- |
| `size` | Dateigröße in Bytes |
| `dim` | Dimensionen |
| `title` | Titel |
| `author` | Autor |
## VisionFusen Erweiterungen
[Abschnitt betitelt „VisionFusen Erweiterungen“](#visionfusen-erweiterungen)
```json
["signed_by", "VisionFusen"],
["signed_by_url", "https://visionfusen.org"]
```
## Weiterführende Links
[Abschnitt betitelt „Weiterführende Links“](#weiterführende-links)
* [NIP-94 Spezifikation](https://github.com/nostr-protocol/nips/blob/master/94.md)
# Konzepte
> Grundlegende Konzepte hinter VisionFusen
Verstehe die Bausteine, auf denen VisionFusen aufbaut.
## Das große Bild
[Abschnitt betitelt „Das große Bild“](#das-große-bild)
```plaintext
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Dein │ │ SHA-256 │ │ NIP-94 │ │ Nostr │
│ Bild │ → │ Hash │ → │ Event │ → │ Relays │
│ + XMP │ │ │ │ Kind 1063 │ │ │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
```
## Die Bausteine
[Abschnitt betitelt „Die Bausteine“](#die-bausteine)
### 1. XMP Metadaten
[Abschnitt betitelt „1. XMP Metadaten“](#1-xmp-metadaten)
Strukturierte Daten IN deinem Bild. VisionFusen liest:
* **dc:creator** (Autor) - PFLICHT
* **dc:title** (Titel)
* **dc:description** (Beschreibung)
[Mehr zu XMP Metadaten →](/concepts/xmp-metadata/)
### 2. SHA-256 Hash
[Abschnitt betitelt „2. SHA-256 Hash“](#2-sha-256-hash)
Ein 64-Zeichen “Fingerabdruck” deines Bildes:
```plaintext
cd4efae6a87d2c4b8f9e3a7b1c5d2e4f6a8b0c3d5e7f9a1b3c5d7e9f1a3b5c7d
```
[Mehr zu Hash & Signatur →](/concepts/hash-signature/)
### 3. NIP-94 Event
[Abschnitt betitelt „3. NIP-94 Event“](#3-nip-94-event)
Ein standardisiertes Nostr Event (Kind 1063) mit allen Metadaten.
[Mehr zu NIP-94 →](/concepts/nip94/)
### 4. Nostr Relays
[Abschnitt betitelt „4. Nostr Relays“](#4-nostr-relays)
Dezentrale Server, die Events speichern und verteilen.
# XMP Metadaten
> Welche Metadaten werden von VisionFusen unterstützt?
**XMP** (Extensible Metadata Platform) ist ein Standard zum Einbetten von Metadaten in Dateien.
## Pflichtfeld
[Abschnitt betitelt „Pflichtfeld“](#pflichtfeld)
| XMP Feld | VisionFusen Tag | Beschreibung |
| ------------ | --------------- | --------------------------------- |
| `dc:creator` | `author` | **PFLICHT** - Name des Erstellers |
Autor ist Pflicht
Ohne gültigen Autor in `dc:creator` wird die Signierung abgelehnt.
## Optionale Felder
[Abschnitt betitelt „Optionale Felder“](#optionale-felder)
| XMP Feld | VisionFusen Tag |
| ---------------- | --------------- |
| `dc:title` | `title` |
| `dc:description` | `content` |
| `dc:rights` | - |
## XMP einbetten
[Abschnitt betitelt „XMP einbetten“](#xmp-einbetten)
### CodeBack Image Optimizer (empfohlen)
[Abschnitt betitelt „CodeBack Image Optimizer (empfohlen)“](#codeback-image-optimizer-empfohlen)
1. Gehe zu [codeback.de/image-optimizer](https://codeback.de/image-optimizer)
2. Lade dein Bild hoch
3. Fülle die Metadaten-Felder aus
4. Klicke “Optimieren & Herunterladen”
### ExifTool (Kommandozeile)
[Abschnitt betitelt „ExifTool (Kommandozeile)“](#exiftool-kommandozeile)
```bash
exiftool -XMP:Creator="Dein Name" bild.jpg
```
## Ungültige Autoren
[Abschnitt betitelt „Ungültige Autoren“](#ungültige-autoren)
Diese Werte werden abgelehnt:
* `unknown`, `unbekannt`
* `anonymous`, `anonym`
* `ai`, `ki`, `generated`
* `test`, `example`
* Alles unter 2 Zeichen
# Auto-Sign Script
> Automatisch alle Webseiten nach dem Build signieren
Das Auto-Sign Script signiert automatisch alle HTML-Seiten nach dem Astro-Build mit VF-1064.
## Schnellstart
[Abschnitt betitelt „Schnellstart“](#schnellstart)
```bash
# Dry Run (zeigt was passieren würde)
npm run sign:dry
# Echt signieren
npm run sign
# Build + Sign in einem
npm run build:sign
```
## Setup
[Abschnitt betitelt „Setup“](#setup)
### 1. Environment Variables
[Abschnitt betitelt „1. Environment Variables“](#1-environment-variables)
Erstelle `.env` im Projektroot:
```env
# Pflicht
BUNKER_API_TOKEN=dein-token
# Optional (hat Defaults)
BUNKER_KEY_NAME=steven
BUNKER_URL=https://bunker.visionfusen.org
```
Wichtig
Die `.env` Datei darf **nicht** nach GitHub gepusht werden. Sie ist bereits in `.gitignore`.
### 2. Token bekommen
[Abschnitt betitelt „2. Token bekommen“](#2-token-bekommen)
Der `BUNKER_API_TOKEN` kommt aus Railway:
1. Öffne [Railway Dashboard](https://railway.app)
2. Projekt: `visionfusen-bunker`
3. Tab: Variablen
4. Kopiere `BUNKER_API_TOKEN`
### 3. Key-Name finden
[Abschnitt betitelt „3. Key-Name finden“](#3-key-name-finden)
```bash
curl -H "Authorization: Bearer DEIN_TOKEN" \
https://bunker.visionfusen.org/api/keys
```
Der `"name"` Wert ist dein `BUNKER_KEY_NAME`.
## Was passiert?
[Abschnitt betitelt „Was passiert?“](#was-passiert)
```plaintext
HTML-Datei
│
▼
Content extrahieren
│
▼
SHA-256 Hash berechnen
│
▼
Bunker API signiert
│
▼
Meta-Tags einfügen
│
▼
Signierte HTML-Datei
```
## Output
[Abschnitt betitelt „Output“](#output)
Nach dem Signieren enthält jede HTML-Datei:
```html
```
## Cache
[Abschnitt betitelt „Cache“](#cache)
Das Script cached Hashes in `.sign-cache.json`. Beim nächsten Run werden nur geänderte Seiten neu signiert:
```plaintext
✅ Signiert: 3 (geändert)
⏭️ Unverändert: 17 (aus Cache)
```
## Optionen
[Abschnitt betitelt „Optionen“](#optionen)
| Flag | Beschreibung |
| ----------- | ----------------------------------------- |
| `--dry-run` | Zeigt was passieren würde, signiert nicht |
| `--verbose` | Ausführliche Ausgabe |
## Fehlerbehebung
[Abschnitt betitelt „Fehlerbehebung“](#fehlerbehebung)
### ”BUNKER\_API\_TOKEN nicht gesetzt”
[Abschnitt betitelt „”BUNKER\_API\_TOKEN nicht gesetzt”“](#bunker_api_token-nicht-gesetzt)
Die `.env` Datei fehlt oder der Token ist leer.
### ”Key nicht gefunden”
[Abschnitt betitelt „”Key nicht gefunden”“](#key-nicht-gefunden)
Der `BUNKER_KEY_NAME` stimmt nicht. Prüfe mit:
```bash
curl -H "Authorization: Bearer TOKEN" \
https://bunker.visionfusen.org/api/keys
```
### “Key ist gesperrt”
[Abschnitt betitelt „“Key ist gesperrt”“](#key-ist-gesperrt)
Der Key muss im Bunker entsperrt sein. Öffne das Admin-Dashboard oder nutze Auto-Unlock.
## Siehe auch
[Abschnitt betitelt „Siehe auch“](#siehe-auch)
* [VF-1064 Spec](/reference/vf-1064) – Technische Spezifikation
* [Hash & Signatur](/concepts/hash-signature) – Wie Signaturen funktionieren
# Badge einbinden
> Zeige die Verifizierung auf deiner Website
Zeige Besuchern, dass dein Bild verifiziert ist.
## Dynamisches Badge
[Abschnitt betitelt „Dynamisches Badge“](#dynamisches-badge)
Das Badge wird live generiert und zeigt aktuellen Status:
### HTML
[Abschnitt betitelt „HTML“](#html)
```html
```
### Mit Link zum Event
[Abschnitt betitelt „Mit Link zum Event“](#mit-link-zum-event)
```html
```
### Markdown
[Abschnitt betitelt „Markdown“](#markdown)
```markdown
[](https://njump.me/EVENT_ID)
```
## Badge-Parameter
[Abschnitt betitelt „Badge-Parameter“](#badge-parameter)
| Parameter | Beschreibung |
| --------- | ------------------------------ |
| `hash` | SHA-256 Hash des Bildes |
| `event` | Nostr Event ID |
| `url` | Bild-URL (Hash wird berechnet) |
## Badge-Styles
[Abschnitt betitelt „Badge-Styles“](#badge-styles)
| Status | Farbe | Text |
| ----------------- | --------- | ------------- |
| Verifiziert | 🟢 Grün | Autor/Datum ✓ |
| Nicht verifiziert | 🟡 Orange | not verified |
| Fehler | 🔴 Rot | error |
## Embed-Code aus API
[Abschnitt betitelt „Embed-Code aus API“](#embed-code-aus-api)
Die Verify-API liefert fertigen Embed-Code:
```json
{
"embed": {
"html_badge": "
",
"markdown": "[](https://njump.me/...)"
}
}
```
# Example Guide
> A guide in my new Starlight docs site.
Guides lead a user through a specific task they want to accomplish, often with a sequence of steps. Writing a good guide requires thinking about what your users are trying to do.
## Further reading
[Abschnitt betitelt „Further reading“](#further-reading)
* Read [about how-to guides](https://diataxis.fr/how-to-guides/) in the Diátaxis framework
# Einführung
> Was ist VisionFusen und warum brauchst du es?
**Medien-Pipeline für das neue Internet.**
Normaler Web-Content rein. Signierter, indexer-ready Output raus. Die Brücke zwischen Web2-Publishing und Web3-Ownership.
## Das Problem
[Abschnitt betitelt „Das Problem“](#das-problem)
Content Discovery ist der größte Pain Point im dezentralen Web. Wer hat was wann erstellt? Wie wird Content gefunden? Wie monetarisiert man ohne Plattform-Lock-in?
* **Ownership** — Wer hat das Original erstellt?
* **Discovery** — Wie finden Menschen (und Maschinen) deinen Content?
* **Monetarisierung** — Wie verdienst du ohne Mittelsmänner?
## Die Lösung: VisionFusen Pipeline
[Abschnitt betitelt „Die Lösung: VisionFusen Pipeline“](#die-lösung-visionfusen-pipeline)
```plaintext
Content → Optimierung → Metadaten → Signatur → Distribution
```
### 1. Optimierung
[Abschnitt betitelt „1. Optimierung“](#1-optimierung)
WebP/AVIF, saubere Struktur, komprimiert für Edge-Delivery.
### 2. Metadaten
[Abschnitt betitelt „2. Metadaten“](#2-metadaten)
Schema.org, JSON-LD — maschinenlesbar für LLMs und Indexer.
### 3. Signatur
[Abschnitt betitelt „3. Signatur“](#3-signatur)
NIP-94 (Bilder), NIP-23 (Texte) — kryptografischer Beweis auf Nostr.
### 4. Distribution
[Abschnitt betitelt „4. Distribution“](#4-distribution)
Gleichzeitig auf Nostr Relays und klassischem Web.
## LLMO — LLM Optimization
[Abschnitt betitelt „LLMO — LLM Optimization“](#llmo--llm-optimization)
Nicht nur für Menschen lesbar, sondern für Maschinen verstehbar.
* **Schema.org** — Strukturierte Daten
* **JSON-LD** — Maschinenlesbare Metadaten
* **llms.txt** — Direkter Zugang für AI-Crawlers
* **Citation-Optimized** — Deine Seiten tauchen als Quelle in KI-Antworten auf
## Drei Monetarisierungs-Layer
[Abschnitt betitelt „Drei Monetarisierungs-Layer“](#drei-monetarisierungs-layer)
| Layer | Zielgruppe | Mechanismus |
| --------- | ------------ | -------------------------------- |
| **Human** | Web-Besucher | Paywall via LNbits, Pay-per-Post |
| **Nostr** | Community | Value-for-Value, Zaps |
| **Agent** | AI-Agenten | L402 API, Sats per Call |
## Für wen?
[Abschnitt betitelt „Für wen?“](#für-wen)
* **Content Creator** — Sichere Ownership, mehrere Monetarisierungswege
* **Coaches & Consultants** — Signierte Materialien, eigene Infrastruktur
* **Entwickler** — API-First, selbst hostbar, erweiterbar
* **Early Adopters** — Bau mit am dezentralen Content-Stack
## Nächste Schritte
[Abschnitt betitelt „Nächste Schritte“](#nächste-schritte)
→ [Schnellstart](/guides/quickstart/) — Erstes Bild signieren\
→ [Konzepte](/concepts/overview/) — NIP-94, Hashes, Signaturen verstehen\
→ [API Reference](/api/) — Für Entwickler
# Schnellstart
> In 5 Minuten deine erste Datei signieren
Signiere deine erste Datei in unter 5 Minuten.
## Unterstützte Formate
[Abschnitt betitelt „Unterstützte Formate“](#unterstützte-formate)
| Typ | Formate | Autor-Feld |
| ------------- | -------------------- | -------------------------- |
| **Bilder** | WebP, PNG, JPEG, GIF | XMP `dc:creator` |
| **Dokumente** | PDF | PDF-Info `/Author` |
| **Text** | Markdown, TXT | YAML Frontmatter `author:` |
## Voraussetzung: Autor einbetten
[Abschnitt betitelt „Voraussetzung: Autor einbetten“](#voraussetzung-autor-einbetten)
VisionFusen erfordert einen **Autor**. Ohne Autor wird die Signierung abgelehnt.
### Bilder: XMP-Metadaten
[Abschnitt betitelt „Bilder: XMP-Metadaten“](#bilder-xmp-metadaten)
**Option A: CodeBack Image Optimizer (empfohlen)**
1. Gehe zu [codeback.de/image-optimizer](https://codeback.de/image-optimizer)
2. Lade dein Bild hoch
3. Fülle das Feld “Autor” aus
4. Klicke “Optimieren & Herunterladen”
**Option B: ExifTool**
```bash
exiftool -XMP:Creator="Dein Name" bild.jpg
```
### Markdown/Text: YAML Frontmatter
[Abschnitt betitelt „Markdown/Text: YAML Frontmatter“](#markdowntext-yaml-frontmatter)
```markdown
---
title: Mein Dokument
author: Dein Name
description: Kurze Beschreibung
---
# Inhalt hier...
```
### PDF: Dokument-Eigenschaften
[Abschnitt betitelt „PDF: Dokument-Eigenschaften“](#pdf-dokument-eigenschaften)
Setze den Autor in deinem PDF-Editor unter Dokument-Eigenschaften.
## Datei signieren
[Abschnitt betitelt „Datei signieren“](#datei-signieren)
### Web-Interface (empfohlen)
[Abschnitt betitelt „Web-Interface (empfohlen)“](#web-interface-empfohlen)
1. Gehe zu [visionfusen.org/tools/sign](https://visionfusen.org/tools/sign)
2. Ziehe deine Datei in den Upload-Bereich
3. Prüfe die erkannten Metadaten
4. Klicke “Signieren”
Fertig! Du erhältst:
* CDN-URL deiner Datei
* Verifizierungs-Link
* LLM-optimierte Event-Page
* NIP-94 Event für Nostr
### Via API
[Abschnitt betitelt „Via API“](#via-api)
```bash
# Bild signieren
curl -X POST https://visionfusen.org/api/sign-image-v2 \
-F "file=@mein-bild.webp"
# Markdown signieren
curl -X POST https://visionfusen.org/api/sign-image-v2 \
-F "file=@mein-dokument.md"
# PDF signieren
curl -X POST https://visionfusen.org/api/sign-image-v2 \
-F "file=@mein-dokument.pdf"
```
Antwort:
```json
{
"success": true,
"hash": "8a97b9bedaddf...",
"file_type": "text",
"mime_type": "text/markdown",
"metadata": {
"author": "Steven Noack",
"title": "VisionFusen - Roadmap & Ideen"
},
"urls": {
"file": "https://cdn.visionfusen.org/documents/...",
"static_page": "https://cdn.visionfusen.org/events/.../index.html",
"verify": "https://visionfusen.org/verify?hash=8a97b9be..."
}
}
```
## Signatur verifizieren
[Abschnitt betitelt „Signatur verifizieren“](#signatur-verifizieren)
Jeder kann die Signatur prüfen:
```bash
curl "https://visionfusen.org/api/verify?hash=8a97b9be..."
```
Oder via Web: [visionfusen.org/verify](https://visionfusen.org/verify)
## Nächste Schritte
[Abschnitt betitelt „Nächste Schritte“](#nächste-schritte)
* [Konzepte: NIP-94](/concepts/nip94/) — Wie funktioniert die Signierung?
* [API Reference](/api/) — Vollständige API-Dokumentation
* [Badge einbetten](/guides/embed-badge/) — Verifizierungs-Badge auf deiner Seite
# Bild signieren
> Verschiedene Wege zum Signieren deiner Bilder
Es gibt mehrere Wege, dein Bild mit VisionFusen zu signieren.
## Web-Interface
[Abschnitt betitelt „Web-Interface“](#web-interface)
Der einfachste Weg für einzelne Bilder.
### Schritt 1: Bild vorbereiten
[Abschnitt betitelt „Schritt 1: Bild vorbereiten“](#schritt-1-bild-vorbereiten)
Stelle sicher, dass dein Bild XMP-Metadaten mit Autor hat:
* [CodeBack Image Optimizer](https://codeback.de/image-optimizer) nutzen
* Oder eigenes Tool (Lightroom, ExifTool, etc.)
### Schritt 2: Signierungsmethode wählen
[Abschnitt betitelt „Schritt 2: Signierungsmethode wählen“](#schritt-2-signierungsmethode-wählen)
Gehe zu [visionfusen.org/tools/sign](https://visionfusen.org/tools/sign) und wähle:
| Methode | Beschreibung | Kosten |
| ------------------------------ | ------------------------------------- | --------- |
| **Browser Extension (NIP-07)** | Signiere mit deinem eigenen Nostr-Key | Kostenlos |
| **VisionFusen Zeitstempel** | VisionFusen signiert als Zeuge | 21 Sats |
## API
[Abschnitt betitelt „API“](#api)
Für Automatisierung und Integration in eigene Workflows.
### cURL Beispiel
[Abschnitt betitelt „cURL Beispiel“](#curl-beispiel)
```bash
# Mit Datei-Upload
curl -X POST https://visionfusen.org/api/sign-image-v2 \
-F "image=@mein-bild.webp"
# Mit URL
curl -X POST https://visionfusen.org/api/sign-image-v2 \
-H "Content-Type: application/json" \
-d '{"image_url": "https://example.com/bild.webp"}'
```
### JavaScript/Node.js
[Abschnitt betitelt „JavaScript/Node.js“](#javascriptnodejs)
```javascript
const formData = new FormData();
formData.append('image', file);
const response = await fetch('https://visionfusen.org/api/sign-image-v2', {
method: 'POST',
body: formData
});
const result = await response.json();
console.log(result.urls.verify);
```
## Fehlerbehandlung
[Abschnitt betitelt „Fehlerbehandlung“](#fehlerbehandlung)
| Error Code | Bedeutung | Lösung |
| ----------------- | --------------------- | --------------------------- |
| `AUTHOR_REQUIRED` | Kein Autor in XMP | Metadaten einbetten |
| `ALREADY_SIGNED` | Bild bereits signiert | Verifizierungs-URL nutzen |
| `FILE_TOO_LARGE` | Über 10MB | Bild komprimieren |
| `INVALID_FORMAT` | Falsches Format | WebP, PNG, JPEG, GIF nutzen |
# Signatur verifizieren
> Wie du prüfst ob ein Bild signiert wurde
Jeder kann prüfen, ob ein Bild auf Nostr signiert wurde.
## Web-Interface
[Abschnitt betitelt „Web-Interface“](#web-interface)
Der einfachste Weg:
1. Gehe zu [visionfusen.org/verify](https://visionfusen.org/verify)
2. Gib den SHA-256 Hash ein ODER lade das Bild hoch
3. Ergebnis wird angezeigt
## API
[Abschnitt betitelt „API“](#api)
### Per Hash
[Abschnitt betitelt „Per Hash“](#per-hash)
```bash
curl "https://visionfusen.org/api/verify?hash=cd4efae6..."
```
### Per Bild-URL
[Abschnitt betitelt „Per Bild-URL“](#per-bild-url)
```bash
curl "https://visionfusen.org/api/verify?url=https://cdn.visionfusen.org/images/bild.webp"
```
### Antwort: Signiert
[Abschnitt betitelt „Antwort: Signiert“](#antwort-signiert)
```json
{
"verified": true,
"hash": "cd4efae6...",
"event": {
"id": "9bc481...",
"pubkey": "abc123...",
"npub": "npub1...",
"created_at": 1703001234
},
"metadata": {
"author": "Steven Noack",
"title": "Sonnenuntergang"
},
"relays_found": ["relay.damus.io", "nos.lol"]
}
```
### Antwort: Nicht signiert
[Abschnitt betitelt „Antwort: Nicht signiert“](#antwort-nicht-signiert)
```json
{
"verified": false,
"hash": "cd4efae6...",
"message": "Keine Signatur gefunden",
"sign_url": "https://visionfusen.org/tools/sign"
}
```
## Hash selbst berechnen
[Abschnitt betitelt „Hash selbst berechnen“](#hash-selbst-berechnen)
### JavaScript/Browser
[Abschnitt betitelt „JavaScript/Browser“](#javascriptbrowser)
```javascript
async function calculateHash(file) {
const buffer = await file.arrayBuffer();
const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
return Array.from(new Uint8Array(hashBuffer))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
```
### Bash
[Abschnitt betitelt „Bash“](#bash)
```bash
sha256sum bild.webp | cut -d' ' -f1
```
# Example Reference
> A reference page in my new Starlight docs site.
Reference pages are ideal for outlining how things work in terse and clear terms. Less concerned with telling a story or addressing a specific use case, they should give a comprehensive outline of what you’re documenting.
## Further reading
[Abschnitt betitelt „Further reading“](#further-reading)
* Read [about reference](https://diataxis.fr/reference/) in the Diátaxis framework
# VF-1064: Signed Web Pages
> VisionFusen Spezifikation für signierte Webseiten
**Status:** Draft\
**Autor:** Steven Noack\
**Erstellt:** 20. Dezember 2025\
**Kind:** 1064 (regular) / 30064 (replaceable)
***
## Abstract
[Abschnitt betitelt „Abstract“](#abstract)
VF-1064 definiert eine Methode zur kryptografischen Signierung von Webseiten mittels Nostr-Keys. Die Signatur bezieht sich auf einen kanonischen Hash des Seiteninhalts und ermöglicht die Verifizierung von Autorenschaft und Integrität.
***
## Motivation
[Abschnitt betitelt „Motivation“](#motivation)
Webseiten haben kein eingebautes Konzept für Autorenschaft oder Integrität. HTTPS schützt den Transport, nicht den Inhalt. Jeder kann behaupten, Autor einer Seite zu sein.
**Probleme heute:**
* Keine verifizierbare Autorenschaft
* Keine Integritätsprüfung des Inhalts
* LLMs können Quellen nicht kryptografisch verifizieren
* Plagiate sind schwer nachweisbar
**VF-1064 löst das:**
* Autor signiert mit Nostr-Key
* Hash des Inhalts ist verifizierbar
* Zeitstempel ist kryptografisch fixiert
* LLMs können Trust-Level ableiten
***
## Spezifikation
[Abschnitt betitelt „Spezifikation“](#spezifikation)
### Event Kind
[Abschnitt betitelt „Event Kind“](#event-kind)
| Kind | Typ | Use Case |
| ------- | ----------- | -------------------------------------- |
| `1064` | Regular | Jede Version bleibt erhalten (History) |
| `30064` | Replaceable | Nur aktuelle Version zählt |
### Event Struktur
[Abschnitt betitelt „Event Struktur“](#event-struktur)
```json
{
"kind": 1064,
"content": "",
"tags": [
["d", "visionfusen.org/vision"],
["url", "https://visionfusen.org/vision"],
["x", "a1b2c3d4e5f6..."],
["m", "text/html"],
["title", "Vision & Roadmap"],
["selector", "main"],
["snapshot", "2025-12-20T10:30:00Z"],
["signed_by", "VisionFusen"],
["signed_by_url", "https://visionfusen.org"]
],
"pubkey": "bef829d0...",
"created_at": 1734692000,
"sig": "..."
}
```
### Tags
[Abschnitt betitelt „Tags“](#tags)
#### Pflicht-Tags
[Abschnitt betitelt „Pflicht-Tags“](#pflicht-tags)
| Tag | Beschreibung | Beispiel |
| ----- | ---------------------------------- | -------------------------- |
| `url` | Canonical URL der signierten Seite | `https://example.com/page` |
| `x` | SHA-256 Hash des Inhalts | `a1b2c3d4...` (64 Zeichen) |
| `m` | MIME Type | `text/html` |
#### Optionale Tags
[Abschnitt betitelt „Optionale Tags“](#optionale-tags)
| Tag | Beschreibung | Beispiel |
| ---------- | ----------------------------------- | ----------------------------- |
| `d` | Identifier für replaceable Events | `example.com/page` |
| `title` | Seitentitel | `Vision & Roadmap` |
| `selector` | CSS-Selector für signierten Bereich | `main`, `article`, `#content` |
| `snapshot` | ISO-Timestamp der Hash-Berechnung | `2025-12-20T10:30:00Z` |
| `previous` | Event-ID der vorherigen Version | `abc123...` |
| `author` | Autorenname | `Steven Noack` |
| `lang` | Sprache | `de`, `en` |
#### VisionFusen-Tags
[Abschnitt betitelt „VisionFusen-Tags“](#visionfusen-tags)
| Tag | Beschreibung |
| --------------- | ---------------- |
| `signed_by` | Signatur-Service |
| `signed_by_url` | URL des Services |
***
## Hash-Berechnung
[Abschnitt betitelt „Hash-Berechnung“](#hash-berechnung)
### Variante A: Full Page Hash
[Abschnitt betitelt „Variante A: Full Page Hash“](#variante-a-full-page-hash)
Der gesamte HTML-Body wird gehasht:
```javascript
const html = await fetch(url).then(r => r.text());
const hash = await sha256(html);
```
**Problem:** Jede kleine Änderung (Ads, Timestamp, Session-ID) ändert den Hash.
### Variante B: Selector-Based Hash (empfohlen)
[Abschnitt betitelt „Variante B: Selector-Based Hash (empfohlen)“](#variante-b-selector-based-hash-empfohlen)
Nur der relevante Content wird gehasht:
```javascript
const html = await fetch(url).then(r => r.text());
const doc = parseHTML(html);
const content = doc.querySelector(selector).innerHTML;
const normalized = normalize(content); // Whitespace, etc.
const hash = await sha256(normalized);
```
**Empfohlene Selektoren:**
* `main` – Hauptinhalt
* `article` – Artikel-Content
* `#content` – Spezifischer Container
* `.prose` – Content-Bereich
### Normalisierung
[Abschnitt betitelt „Normalisierung“](#normalisierung)
Vor dem Hashen wird der Content normalisiert:
1. Whitespace komprimieren
2. Leere Tags entfernen
3. Attribute sortieren
4. Self-closing Tags vereinheitlichen
```javascript
function normalize(html) {
return html
.replace(/\s+/g, ' ') // Whitespace
.replace(/>\s+<') // Zwischen Tags
.trim();
}
```
***
## HTML-Embedding
[Abschnitt betitelt „HTML-Embedding“](#html-embedding)
Signierte Seiten KÖNNEN Verifikations-Metadaten einbetten:
```html
```
***
## Verifikations-Prozess
[Abschnitt betitelt „Verifikations-Prozess“](#verifikations-prozess)
```plaintext
1. Event von Relay holen (via Event-ID oder d-Tag)
│
▼
2. URL aus Event extrahieren
│
▼
3. Seite fetchen
│
▼
4. Content extrahieren (via selector oder full body)
│
▼
5. Normalisieren
│
▼
6. SHA-256 berechnen
│
▼
7. Mit x-Tag vergleichen
│
▼
8. Event-Signatur prüfen
│
▼
9. ✅ Verifiziert oder ❌ Fehlgeschlagen
```
***
## Versionierung
[Abschnitt betitelt „Versionierung“](#versionierung)
### Mit Regular Events (Kind 1064)
[Abschnitt betitelt „Mit Regular Events (Kind 1064)“](#mit-regular-events-kind-1064)
Jede Version ist ein separates Event:
```plaintext
Version 1: event-id-abc (created_at: 1000)
Version 2: event-id-def (created_at: 2000, ["previous", "event-id-abc"])
Version 3: event-id-ghi (created_at: 3000, ["previous", "event-id-def"])
```
**Vorteile:** Vollständige History, Audit Trail
### Mit Replaceable Events (Kind 30064)
[Abschnitt betitelt „Mit Replaceable Events (Kind 30064)“](#mit-replaceable-events-kind-30064)
Nur die neueste Version zählt:
```plaintext
Event mit ["d", "example.com/page"]
→ Neues Event mit gleichem d-Tag ersetzt das alte
```
**Vorteile:** Weniger Noise, klare “aktuelle Wahrheit”
***
## Use Cases
[Abschnitt betitelt „Use Cases“](#use-cases)
### 1. LLM Trust Signal
[Abschnitt betitelt „1. LLM Trust Signal“](#1-llm-trust-signal)
LLMs können signierte Seiten höher gewichten:
```plaintext
Unsignierte Quelle: Trust 0.5
Signierte Quelle: Trust 0.8
Signiert + bekannter Autor: Trust 0.95
```
### 2. Content Provenance
[Abschnitt betitelt „2. Content Provenance“](#2-content-provenance)
Nachweis wer was wann publiziert hat:
> “Diese Seite wurde am 20.12.2025 von npub1… signiert.”
### 3. Anti-Plagiat
[Abschnitt betitelt „3. Anti-Plagiat“](#3-anti-plagiat)
Originalautor kann Erstveröffentlichung beweisen:
> “Mein signiertes Event ist älter als deren Kopie.”
### 4. Archivierung
[Abschnitt betitelt „4. Archivierung“](#4-archivierung)
Signierte Snapshots für Wayback-Style Archive:
```plaintext
snapshot-2025-12-20.vf1064-abc123.html
snapshot-2025-12-21.vf1064-def456.html
```
***
## Dateinamenskonvention
[Abschnitt betitelt „Dateinamenskonvention“](#dateinamenskonvention)
VisionFusen nutzt folgendes Schema:
```plaintext
{name}.vf{kind}-{hash8}.{ext}
```
**Beispiele:**
```plaintext
vision-page.vf1064-3f7a2c1d.html
about.vf30064-8e9f0a1b.html
blog-post.vf1064-c2d3e4f5.html
```
***
## Implementierung
[Abschnitt betitelt „Implementierung“](#implementierung)
### Sign Tool (Pseudocode)
[Abschnitt betitelt „Sign Tool (Pseudocode)“](#sign-tool-pseudocode)
```javascript
async function signWebPage(url, selector = 'main') {
// 1. Fetch
const html = await fetch(url).then(r => r.text());
// 2. Extract
const doc = parseHTML(html);
const content = doc.querySelector(selector)?.innerHTML || html;
// 3. Normalize
const normalized = normalize(content);
// 4. Hash
const hash = await sha256(normalized);
// 5. Create Event
const event = {
kind: 1064,
content: '',
tags: [
['url', url],
['x', hash],
['m', 'text/html'],
['selector', selector],
['snapshot', new Date().toISOString()],
['title', doc.querySelector('title')?.textContent || ''],
],
created_at: Math.floor(Date.now() / 1000),
};
// 6. Sign (via NIP-46 Bunker)
const signed = await bunker.signEvent(event);
// 7. Publish
await relay.publish(signed);
return signed;
}
```
### Verify Tool (Pseudocode)
[Abschnitt betitelt „Verify Tool (Pseudocode)“](#verify-tool-pseudocode)
```javascript
async function verifyWebPage(eventId) {
// 1. Fetch Event
const event = await relay.get(eventId);
// 2. Extract Tags
const url = event.tags.find(t => t[0] === 'url')?.[1];
const expectedHash = event.tags.find(t => t[0] === 'x')?.[1];
const selector = event.tags.find(t => t[0] === 'selector')?.[1] || 'body';
// 3. Fetch Page
const html = await fetch(url).then(r => r.text());
const doc = parseHTML(html);
const content = doc.querySelector(selector)?.innerHTML || html;
// 4. Calculate Hash
const actualHash = await sha256(normalize(content));
// 5. Compare
if (actualHash !== expectedHash) {
return { valid: false, reason: 'Hash mismatch - content changed' };
}
// 6. Verify Signature
if (!verifySignature(event)) {
return { valid: false, reason: 'Invalid signature' };
}
return {
valid: true,
author: event.pubkey,
signedAt: new Date(event.created_at * 1000),
};
}
```
***
## Offene Fragen
[Abschnitt betitelt „Offene Fragen“](#offene-fragen)
1. **Dynamische Seiten:** Wie umgehen mit Server-Rendered Content der sich ändert?
2. **SPAs:** Wie hasht man eine React/Vue App?
3. **Canonical URL:** Was wenn eine Seite mehrere URLs hat?
4. **Robots/Crawling:** Sollte es ein `nostr-verification.json` geben (wie `robots.txt`)?
***
## Vergleich mit Alternativen
[Abschnitt betitelt „Vergleich mit Alternativen“](#vergleich-mit-alternativen)
| Methode | Dezentral | Verifizierbar | Timestamp |
| ----------------- | ------------ | ---------------- | --------- |
| SSL/TLS | ❌ CA-basiert | ❌ Nur Transport | ❌ |
| Meta Author Tag | ✅ | ❌ Nicht signiert | ❌ |
| Blockchain Notary | ✅ | ✅ | ✅ Teuer |
| **VF-1064** | ✅ | ✅ | ✅ Gratis |
***
## Weiterführende Links
[Abschnitt betitelt „Weiterführende Links“](#weiterführende-links)
* [NIP-94: File Metadata](/concepts/nip94) – Basis für Datei-Signaturen
* [Hash & Signatur erklärt](/concepts/hash-signature) – Technische Grundlagen
* [VisionFusen Sign Tool](https://visionfusen.org/sign) – Implementierung
***
*VisionFusen Spezifikation – Dezember 2025*