The Problem#
Centralized search is not a technical necessity. It is an architectural choice. The choice to run one crawler, store one index, and enforce one ranking algorithm means:
- One entity decides what gets crawled — and what does not
- One entity decides what gets indexed — and what gets buried
- One entity decides what ranking signals matter — and which sites disappear
- Every query is logged, profiled, and monetized
SIP-01 makes the opposite choice at every layer.
The Protocol: What SIP-01 Actually Defines#
SIP-01 (Search Index Protocol) is an open Nostr standard for decentralized web search indexing. The spec is deliberately minimal — it defines exactly one thing:
What an indexed web document looks like on Nostr.
It explicitly does not define ranking algorithms, moderation policy, branding, user identity, NIP-50 search syntax, or relay-internal scoring. Those belong to engines, relays, and higher layers. This separation is the architectural insight: by freezing the document format and leaving everything else competitive, SIP-01 creates a shared substrate that any engine can build on without permission.
The Event: Kind 39697#
Every SIP-01 observation is a single addressable Nostr event. Here is a real, fully self-consistent example from the spec:
{
"kind": 39697,
"pubkey": "<indexer pubkey>",
"created_at": 1786250000,
"content": "{\"title\":\"Example Page\",\"description\":\"A page about examples.\"}",
"tags": [
["d", "widx:3641c5f2274c5471278ab5bf1df6d185"],
["u", "https://example.com/page"],
["t", "nostr"],
["l", "en"],
["x", "2a5cbdf44513f552fb571d6c6de2ddf16c5452b235cc887980b52898fb38e7c1"],
["v", "1"],
["source", "crawlstr/1"],
["alt", "Web index observation: Example Page"]
]
}
Three identities, deliberately separated:
| Identity | Field | Derivation | Purpose |
|---|---|---|---|
| URL identity | d tag |
widx: + sha256(normalized_url)[0:32] |
Identical across ALL indexers — the shared slot |
| Canonical URL | u tag |
Normalized per spec §7 | The page's preferred URL |
| Content identity | x tag |
sha256(title + "\n" + description) | Agreement signal — did indexers see the same content? |
The widx: namespace prefix prevents collisions with other addressable schemas on Nostr.
Required Fields#
A valid SIP-01 event must carry exactly:
d— onewidx:+ 32 lowercase hex chars, MUST equal the SHA-256 of the normalizedutagu— one valid http(s) URL, ≤ 2048 chars, passing the §11 allowlisttitle— in content JSON, 1–300 chars after trimv— schema version, currently"1"(frozen)alt— human-readable summary, ≤ 1000 chars (NIP-31 convention)
Optional fields include description (≤1000 chars), image (https URL), t topic tags (0–8, relay-filterable), l language (ISO 639-1), x content hash, published (the page's own claimed time), and source (indexer software identifier). Critically: a consumer that only understands d, u, title, and v has a working index entry. Optional fields must never be required to interpret the event.
URL Normalization: The Deterministic Foundation#
Before hashing into d, URLs are normalized through a strict 8-step process:
- Parse; reject non-http(s)
- Lowercase scheme and host; strip leading
www. - Remove default ports (:80 http, :443 https)
- Remove the fragment entirely
- Remove known tracking parameters (utm_*, fbclid, gclid, etc.) — all other query params preserved
- Sort remaining query params alphabetically by key
- Remove trailing
/from path (except bare root) - Re-encode via WHATWG URL semantics
This is not cosmetic. Because every implementation normalizes identically, every crawler observing https://www.Example.com/page?utm_source=x#top produces the exact same d tag. The reference implementation lives in normalizeIndexUrl() and is mirrored byte-compatibly across Crawlstr, sip-01-core, and the UNCAGED Index Relay. 45 conformance tests pin the §13 test vectors byte-for-byte. If your implementation drifts, the tests tell you exactly where.
Why Kind 39697 (and Not NIP-78)#
SIP-01 uses an addressable kind in the 30000–39999 range (formerly NIP-33, now folded into NIP-01). Addressability is the key design decision:
- A crawler re-observing a page updates its previous observation — one live slot per
(pubkey, d)pair. Relay storage stays bounded. - Multiple independent indexers observing the same URL produce events with the same
dtag and different pubkeys — the foundation of the "N independent indexers saw this page" trust model. - The trade-off is no built-in observation history. Search nodes that want history archive every version; relays MAY preserve superseded versions.
NIP-78 (kind 30078) was explicitly rejected: it is designed for applications that "do not care about interoperability" — the exact opposite of a shared index. Historical 0xSearchstr query caches on kind 30078 remain readable as frozen legacy data, but all new indexing uses kind 39697.
Content Identity: The x Hash#
x = sha256(title + "\n" + description). This is a cheap agreement signal, not a full-content hash:
- Same
d, samex→ indexers observed the same content - Same
d, differentx→ the page changed, or indexers disagree - Both outcomes are useful to search nodes
It is deliberately not a hash of the full HTML. Crawlers that want stronger verification can add a full-content hash as an extension tag without changing the wire format.
Extension Tag Registry#
SIP-01 is modular. The core schema is frozen at v: "1", but domain-specific facets ship through a registry:
["type", "repository"], ["platform", "github"], ["network", "clearnet"],
["country", "DE"], ["mime", "application/pdf"]
Rules: extensions MUST be optional, MUST NOT change core field meanings, unknown tags MUST be ignored (forwards compatibility), and single-letter tag names are reserved for relay-filterable fields. Multi-letter extensions are engine-level facets surfaced via NIP-50 operators. Experiments use an x- prefix; registration is a PR into the registry.
Provenance Tiers: Three Kinds of Timestamp#
The spec is unusually careful about temporal trust:
| Timestamp | Source | Trust Level |
|---|---|---|
published_at |
The page's own claim | Lowest — unverifiable |
created_at |
The indexer's signed claim | Medium — a signer can backdate |
first_seen_at |
Relay-local wall clock at index time | Highest — the relay attests this |
Search engines should weight these differently. A page claiming to be from 2020 but first seen by any indexer in 2026 deserves scrutiny.
NIP Dependencies: Minimal by Design#
SIP-01 reuses existing Nostr primitives rather than reinventing them. The complete dependency table:
| NIP | Role | Requirement |
|---|---|---|
| NIP-01 | Event structure, Schnorr signatures, tags, filters, addressable kinds | Required foundation |
| NIP-11 | Relay info document, uncaged_index capability block |
Optional, relay-facing |
| NIP-19 | bech32 identifiers for display | Optional, presentation |
| NIP-45 | COUNT verb for cheap relay-side counts | Optional |
| NIP-50 | search filter + key:value extension mechanism |
Optional acceleration |
| NIP-77 | Negentropy sync for relay federation | Optional federation |
NIP-01 is the only hard dependency. Everything else is optional acceleration. SIP-01 defines only the meaning of its operators on aware relays — it does not require NIP-50 support.
The Ecosystem: Full Stack Breakdown#
SIP-01 is not a paper protocol. Every layer is running in production.
Crawlstr: The Browser Scout#
Role: Lightweight, human-directed discovery — "I found something."
Crawlstr turns any browser into a voluntary crawl node. No backend, no tracking, no account. Key capabilities:
- Random Scout — one button picks a curated starting point from 9 category seed files (nostr, bitcoin, dev, science, books, music, games, opendata, indie) using a weighted strategy (40% fresh, 25% rare, 15% category, 10% stale, 10% random)
- Adaptive recrawls — changed pages revisit in 24h, unchanged pages back off exponentially to a 30-day cap. Unchanged recrawls republish the same
d/xwith freshcreated_at— the network's "still alive" signal - Crawl-trap guards — session-state URLs, filter generators, repeating path segments, 9+ digit counters, depth >8, and 500-URL per-domain intake caps
- Negative cache — permanent failures (4xx, non-HTML, oversize) remembered for 7 days, not retried forever
- Resource honesty — battery/WiFi/bandwidth aware, eco mode, charging-only mode, robots.txt respected, 5–8s per-domain rate limits
- Offline outbox — zero-ack observations held in IndexedDB and flushed when relays are reachable
- Relay-ACKed accounting — "published" counts only relay-confirmed events, never merely-built ones
The v1→v2 upgrade changed behavior, not wire format. Both remain valid SIP-01 (source: crawlstr/1 vs source: crawlstr/v2).
Indexstr: The Distributed Indexing Network#
Role: Systematic, sharded indexing at scale — "I operate indexing capacity."
Indexstr answers the question Crawlstr leaves open: crawl what, exactly?
- 8 curated collections ship as in-browser SQLite databases: Top Sites (~26MB), Awesome Lists (~20MB), RSS Feeds (~19MB), Music (~10MB), Books, Movies, Memes, Video Games
- In-browser SQLite parser — a purpose-built reader (
src/crawler/sqlite.ts) parses collection databases client-side with no WASM - Deterministic sharding — every URL maps to one of 256 shards via FNV-1a; every node's home shard derives from its indexer pubkey. No coordinator, no registration — same seeds in, same assignment out
- Node heartbeats (kind 16919) — running nodes publish signed, privacy-minimal heartbeats (shard, coarse platform, counters). Querying recent heartbeats gives a local estimate of active indexers and shard coverage
- Enrichment layer — every crawled page is classified deterministically: topics ride SIP-01
ttags, document type rides thetypeextension. No AI required; any node replaying the algorithm reproduces the same tags — classification agreement is verifiable - Network discovery intake — while running, a node reads other indexers' kind 39697 observations and queues URLs it has never seen. Every crawler becomes every other node's discovery sensor
- Abuse guards — per-indexer intake caps (Sybil guard), 150k queue ceiling, SSRF guard refusing private/loopback/link-local targets before any request
Indexstr's roadmap includes reputation derivation (search-side scoring from signed observations), sitemap-first discovery, community seed lists over Nostr, headless CLI packaging, and shard leases if sparse-network coverage proves patchy.
UNCAGED-Index-Relay: The SIP-01-Native Relay#
Role: Validated storage, structured indexing, and query serving.
Forked from Ditto Relay (Soapbox), rebuilt with a SIP-01-native layer on OpenSearch:
Validation at ingestion (reject with invalid: on failure):
- Exactly one
d,u,v,alttag dmust equalwidx:+ sha256(normalizedu)[0:32]umust be valid http(s), ≤2048 chars- Content JSON must carry 1–300 char title, optional ≤1000 char description
- When
xis present, verified against sha256(title + "\n" + description) - Topic tags capped at 8, lowercase
Structured indexing — observations become dedicated OpenSearch fields, not generic search text:
url, url_host, url_domain_hierarchy, file_ext, title, description, language, content_hash, published_at, observed_at, source, plus optional extension fields (type, platform, category, network, country, mime).
NIP-50 web-search operators (SIP-01-aware):
site:, domain:, url:, inurl:, title:, topic:, type:, platform:, category:, network:, country:, mime:, filetype:, source:, lang:, before:, after:, distinct:domain
Relay federation via NIP-77:
Two relays reconcile their SIP-01 indexes efficiently: ["NEG-OPEN", "sync", {"kinds": [39697]}, <hex>]. No relay is the permanent global index. If one disappears, the index lives on.
Capability advertisement (NIP-11):
SIP-01-aware relays advertise scope via a custom uncaged_index block: sip01: true, nip50: true, nip77: true, document_kinds: [39697], scope, domains, languages, document_types, filters.
Ranking signals, not rankings:
The relay seeds documents with crawl_score, authority_score, quality_score, and spam_score (all zero at ingest) — reserved for relay-side computed signals. The relay never decides the universal best result. Every engine combines signals its own way.
Full NIP support: NIP-01, 09, 11, 13, 40, 42, 45, 50, 62, 70, 77, 85. Bun runtime, multi-threaded protocol workers, Prometheus metrics.
SIP-Booster-Relay and Crawlstr-SIP-Relay#
Serverless and mobile variants extending relay coverage:
- SIP-Booster-Relay — Cloudflare Workers-based serverless SIP relay
- Crawlstr-SIP-Relay — Android-native SIP relay, bringing index-relay capability to mobile infrastructure
Dsearch: The Flagship Engine#
Role: Community search engine and ecosystem hub. Live at dsearch.com.
Dsearch consolidates the lineage of 0xSearchstr → UNCAGED → Presearchstr into one coherent project. It is where the entire stack becomes user-facing:
Provider architecture:
18 parallel providers return a universal SearchResult[]:
- SIP-01 community index (priority)
- Legacy cache (kind 30078, frozen)
- Nostr NIP-50 search
- Keyword stakes
- Community submissions
- SearXNG, DuckDuckGo, Brave (BYOK), Hacker News
- Wiki (NIP-54), Git (NIP-34), StackOverflow
- Tor-indexed content
Structured query parsing: The search bar parses real syntax into an AST and executes it locally and authoritatively:
"decentralized search" exact phrase
nostr AND privacy boolean (UPPERCASE)
nostr NOT twitter exclusion (-twitter shorthand)
(nostr OR bitcoin) grouping
site:github.com host + subdomains
domain:github.com exact host only
title:"Nostr relay" title match
type:pdf document type
lang:de language
tag:nostr exact topic tag
after:2026-01-01 date boundary
Operators are never "stripped and hoped for." If a relay or engine does not understand an operator, it cannot answer it wrong — evaluation is local.
Query classification and privacy:
| Input | Behavior |
|---|---|
15% of 80 |
Calculator instant answer — no providers run |
npub1... / note1... |
Nostr instant card — clearnet engines never see it |
name@domain.tld |
NIP-05 resolution to profile card |
https://example.com |
SIP-01 index lookup — only Nostr-tier providers run |
| anything else | Full provider fan-out |
Keyword staking: Sign an addressable event binding a keyword to your link. It takes the top "Community Stake" placement on every compatible client. One stake per keyword per npub. No tokens. Recency-ranked today, zap-weighting-ready by schema.
Auto-indexing: Every search contributes surfaced pages back as SIP-01 observations, signed by a per-device pseudonymous keypair — never your identity, never your query.
sip-01-core: The Reference Implementation#
The reusable engine library that any search application can build on:
src/protocol/ — byte-critical SIP-01 code (spec-pinned, 45 tests)
src/federation/ — shared 0xsearchstr:* event contracts (frozen namespaces)
src/lib/ — relay pool, discovery, CORS proxy, URL sanitizers
src/engine/ — 15 providers, query/rank stack, votes, moderation, hooks
src/ai/ — AIProvider contract + OpenAI-compatible layer
Five replacement seams let a host inject identity without forking:
configureEngine()— engine id, indexer software id, Brave tier, AI defaultsconfigureRelays()— default relay pools, localStorage keys- Provider registry — plug in any SearchProvider
- AIProvider — two-method contract or OpenAI-compatible factory
- EngineRuntimeProvider — React runtime for hooks (privacy mode, auto-index, disabled providers, language filter, user signer, moderation set)
The core holds no brand, no trust anchors, no credentials. Currently no LICENSE file — the owner must decide before third-party reuse.
SIP-Dashboard: Network Telemetry#
Live dashboard reading directly from ecosystem relays. No hardcoded indexer registry — any crawler publishing valid kind 39697 or kind 16919 appears automatically.
Tabs: Overview (protocol hero, conformance validation), Network (live nodes, shard coverage, v1/v2 breakdowns), Index (observations/day, top topics/hosts/languages, indexer leaderboard), Ecosystem (per-project profiles with GitHub stats), Protocol (frozen core tag set, extension registry, SIP-02 draft), Settings (editable relay list, NIP-11 probes, NIP-66 auto-discovery).
Everything is derived from events and recomputable client-side. Publishes nothing, tracks no one, trusts nothing it cannot recompute.
The Ancestors: 0xSearchstr, 0xPresearchstr, UNCAGED-ENGINE, Savedd#
- 0xSearchstr — the original search engine aggregator. Established the
0xsearchstr:*federation namespaces. - 0xPresearchstr — community fork adding keyword stakes and Tor/i2p crawling.
- UNCAGED-ENGINE — minimal forkable template. The reference implementation seed.
- Savedd — search-adjacent project in the family.
All read the same shared index. Dsearch and 0xSearchstr federate: same kinds, same tags, different signers. A search on either warms the index for both.
How the Layers Fit Together#
THE WEB
│
▼
┌─────────────────────────────────────────────┐
│ CRAWLSTR (scout) INDEXSTR (indexer) │
│ "I found something" "I operate capacity" │
│ │
│ Random Scout 256-shard partitioning│
│ Adaptive recrawls Curated collections │
│ Per-device keys Node heartbeats │
└──────────────────┬──────────────────────────┘
│ kind 39697, kind 16919
▼
┌─────────────────────────────────────────────┐
│ NOSTR RELAYS (SIP-01-aware) │
│ │
│ UNCAGED-Index-Relay (OpenSearch) │
│ SIP-Booster-Relay (Cloudflare Workers) │
│ Crawlstr-SIP-Relay (Android) │
│ │
│ Validate · Dedupe · Index · Serve │
│ NIP-50 operators · NIP-77 federation │
└──────────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ SEARCH ENGINES (competitive) │
│ │
│ Dsearch (flagship, 18 providers) │
│ 0xSearchstr (original aggregator) │
│ 0xPresearchstr (keyword stakes) │
│ UNCAGED-ENGINE (minimal template) │
│ Your engine here │
│ │
│ Each ranks independently. │
│ Each filters independently. │
│ All read the same index. │
└─────────────────────────────────────────────┘
Crawlstr finds. Indexstr maintains. Relays validate and serve. Engines rank and present. No layer depends on any single entity in another layer.
Why This Architecture Matters#
Indexer Agreement as a Trust Primitive#
When 7 independent indexers observe the same URL with matching x hashes, that is a trust signal no single crawler can fabricate. The deterministic d tag makes this a query, not a guess. The spec cautions: distinct-author counts are independence estimates, not proofs — Sybil keys and key rotation mean engines should weight indexer age, diversity, and content agreement. But the signal exists, it is free, and it gets stronger with every independent indexer that joins.
No Ranking Monopoly#
Google's ranking algorithm is a black box that shapes what billions of people see. SIP-01 relays store signals, not rankings. Every engine is free to combine crawl_score, authority_score, quality_score, spam_score, indexer agreement, freshness, and any other signal however it chooses. Ranking is a competitive layer, not a protocol layer. If you disagree with Dsearch's ranking, you can fork the engine without forking the index.
No Query Surveillance#
Every Google search is logged and monetized. SIP-01 observations contain page metadata — never search queries, never user identity. The auto-indexing keypair is per-device and pseudonymous, cryptographically separate from your personal Nostr identity. Query classification ensures npub inputs never reach clearnet engines. Provider skipping is a privacy feature, not just a speed optimization.
Protocol Minimalism as a Feature#
SIP-01 reuses Nostr primitives instead of inventing new ones. NIP-01 is the only hard dependency. NIP-50, NIP-45, NIP-77, and NIP-11 are optional accelerations. The spec's §20.1 dependency table explicitly states what is taken from each NIP, whether the reference is normative, and what happens when an implementation does not support it. This is unusually honest protocol design.
Frozen Wire Format, Living Ecosystem#
The wire schema is v: "1" and will not change without a spec revision. The spec document is at v1.2 (audited against upstream NIPs). The software layer evolves freely — Crawlstr shipped v2, Indexstr shipped sharding and enrichment, Dsearch shipped structured queries — all without touching the wire format. Protocol stability and software velocity are not in tension here.
Privacy, Honestly#
The ecosystem is upfront about what it does and does not protect:
- Key separation is guaranteed. Crawler keys are per-device, pseudonymous, and never linked to your Nostr identity.
- Network anonymity is not. Relay operators see your IP. Use a VPN or Tor (the docs recommend NymVPN).
- CORS proxy trade-off is disclosed. When direct fetch fails, a CORS proxy is used — the proxy operator sees which URL was fetched (never a search query, never a user identity). The dashboard shows the direct/proxy split.
- Browser limitations are documented. No JavaScript rendering. Mobile browsers may throttle background tabs. Rate limits are respectful by design.
- No login. No account. No tracking. Not as a feature — as the architecture.
The Philosophy#
Everything in this ecosystem is built on the same principles:
- Open source. Spec is CC0. Code is MIT.
- No tracking. No accounts. No query logging. Per-device pseudonymous keys.
- Decentralized. No mandatory identity. No single owner. No mandatory relay. No mandatory engine.
- Modular. Core schema frozen. Extensions via registry. Competitive layers for ranking, filtering, presentation.
- Honest. What the protocol does NOT define is documented as carefully as what it does. Browser limitations, CORS trade-offs, network anonymity gaps — all disclosed.
One shared decentralized index. Many independent indexers. Many independent search nodes. Many independent search engines. No mandatory identity. No single owner.
Try It#
| Layer | Project | Link |
|---|---|---|
| Protocol spec | SIP-01 | sip.shakespeare.wtf |
| Reference implementation | sip-01-core | github.com/NostrDanish/sip-01-core |
| Browser crawler | Crawlstr | crawlstr.shakespeare.wtf |
| Indexing network | Indexstr | github.com/NostrDanish/indexstr |
| Index relay | UNCAGED-Index-Relay | github.com/NostrDanish/UNCAGED-Index-Relay |
| Search engine | Dsearch | dsearch.com |
| Network telemetry | SIP-dashboard | sip.shakespeare.wtf/dashboard |
| Original engine | 0xSearchstr | 0xsearchstr.shakespeare.wtf |
| Community fork | 0xPresearchstr | presearchstr.shakespeare.wtf |
| Engine template | UNCAGED-ENGINE | uncaged.shakespeare.wtf |
Built by 0xPrivacy. Privacy is a human right. Knowledge should remain free. Communities build stronger than corporations.
#nostr #sip01 #decentralized #search #privacy #opensource #protocol

Comments0
No comments yet. Start the conversation.