30817:nip-333-bitcoin-block-headers

NIP-333: Bitcoin Block Headers

Melvin Carvalho

published
2026-10-16

draft optional

Canonical copy: https://nip-333.github.io/spec/ · source: https://github.com/nip-333/nip-333.github.io/blob/gh-pages/spec/nip-333.md

This NIP defines a protocol for distributing Bitcoin block headers over Nostr.

Abstract

Bitcoin block headers are 80-byte cryptographic proofs. This specification defines a minimal, self-contained event for publishing the latest 12 headers via Nostr. The event includes URLs to additional data sources, enabling clients to bootstrap and sync the full header chain.

Event Format

Kind

33333

A parameterized replaceable event (30000-39999 range). Relays store only the latest version for each (pubkey, kind, d-tag) combination. The d tag is the network code, so each network is its own replaceable stream — one publisher key can serve several networks without overwriting itself.

Tags

Tag Required Description
d Yes Network stream identifier: btc, tbtc3, or tbtc4 — MUST equal the n tag
n Yes Network: btc, tbtc3, or tbtc4
tip Recommended Chain tip height (last header in content)
u Recommended Data source URL
p Optional Other trusted publisher pubkey
alt Recommended Human-readable description per NIP-31

Note: tip is recommended but optional — clients can derive height from header chain validation.

Legacy d: latest: earlier drafts of this NIP used d: latest for all networks. Because relays replace on (pubkey, kind, d), a single key publishing two networks would overwrite its own stream. During migration clients MAY query "#d": ["btc", "latest"] and keep the newest verified event.

Networks

Value Network
btc Bitcoin mainnet
tbtc3 Testnet3
tbtc4 Testnet4

Content

Exactly 12 concatenated Bitcoin block headers encoded as lowercase hexadecimal:

length = 12 headers × 80 bytes × 2 = 1,920 characters

Headers in ascending order. The 12th header is the chain tip. First header height = tip - 11.

Example Event

{
  "kind": 33333,
  "pubkey": "cccccccc829b802b7bf52d43edf7cfe62ac89f332a318b6826ac8bd6e73660da",
  "created_at": 1733356800,
  "tags": [
    ["d", "btc"],
    ["n", "btc"],
    ["tip", "875011"],
    ["u", "https://github.com/bitcoincc/bitcoin-epochs/releases/latest/download/btc.archive.bin"],
    ["u", "https://github.com/bitcoincc/bitcoin-current/releases/latest/download/btc.current.bin"],
    ["p", "deadbeef1234567890abcdef1234567890abcdef1234567890abcdef12345678"],
    ["alt", "Bitcoin headers 875000-875011"]
  ],
  "content": "00e0ff3f...[1920 hex chars]...",
  "id": "...",
  "sig": "..."
}

Data Sources

The u tag points to header archive files. Two files provide complete coverage:

File Contents
{network}.archive.bin All completed epochs (immutable, grows each epoch)
{network}.current.bin Current epoch in progress (up to 2016 headers)

The NIP-333 event itself provides the chain tip (latest 12 headers).

URL Schemes

The u tag accepts standard URL schemes:

  • https:// — Remote HTTP(S) resources
  • file:// — Local filesystem paths

Clients SHOULD try URLs in tag order, preferring faster sources. Common local paths include ~/.spv/{network}.archive.bin and ~/.spv/{network}.current.bin for offline operation and reduced bandwidth usage.

Sync Strategy

1. Subscribe to kind:33333 → get tip (12 headers)
         ↓
2. GET btc.current.bin → current epoch
         ↓
3. GET btc.archive.bin → completed epochs (background)
         ↓
4. Stay subscribed for new blocks

Clients validate that headers chain together. If a gap exists between current.bin and the nostr tip, refetch current.bin.

Discovery

  • u tags — Data source URLs (preferred, follow-your-nose)
  • p tags — Other trusted publishers
  • did:nostr — Optional identity via did:nostr

Header Structure

Each 80-byte Bitcoin block header:

Field Bytes Offset Description
Version 4 0 Block version (little-endian)
Previous Hash 32 4 Hash of previous block
Merkle Root 32 36 Transaction merkle root
Timestamp 4 68 Unix timestamp
Bits 4 72 Difficulty target (compact)
Nonce 4 76 Proof-of-work nonce

Subscription

["REQ", "headers", {
  "kinds": [33333],
  "authors": ["cccccccc829b802b7bf52d43edf7cfe62ac89f332a318b6826ac8bd6e73660da"],
  "#d": ["btc"],
  "limit": 1
}]

Verification

Clients SHOULD verify:

  1. Signature — Event signed by trusted pubkey
  2. Length — Exactly 1,920 hex characters (12 headers)
  3. Chain — Each prevHash links to previous header's hash
  4. Proof-of-work — Header hashes meet difficulty targets

Security Considerations

  • Trust: Only subscribe to known, reputable publishers
  • Cross-reference: Compare tips from multiple p tag publishers
  • Relay diversity: Use multiple relays for redundancy
  • Source verification: Validate data from u tag sources

Compact Clients

This NIP supports the Compact Client (CC) framework:

Level Description
CC/0 SPV — Headers + merkle proofs
CC/1 Verify-don't-serve — Full validation via proofs
CC/2 ZK-verified — Zero-knowledge proofs (future)

Implementation

Parsing Headers

function parseHeader(hex, index) {
  const offset = index * 160;
  const headerHex = hex.slice(offset, offset + 160);
  const bytes = new Uint8Array(80);
  for (let i = 0; i < 80; i++) {
    bytes[i] = parseInt(headerHex.slice(i * 2, i * 2 + 2), 16);
  }
  const view = new DataView(bytes.buffer);
  return {
    version: view.getUint32(0, true),
    prevHash: bytesToHex(bytes.slice(4, 36).reverse()),
    merkleRoot: bytesToHex(bytes.slice(36, 68).reverse()),
    timestamp: view.getUint32(68, true),
    bits: view.getUint32(72, true),
    nonce: view.getUint32(76, true)
  };
}

Computing Header Hash

async function computeHeaderHash(headerHex) {
  const bytes = hexToBytes(headerHex);
  const hash1 = await crypto.subtle.digest('SHA-256', bytes);
  const hash2 = await crypto.subtle.digest('SHA-256', hash1);
  return bytesToHex(new Uint8Array(hash2).reverse());
}

References

Cited links

Discussion

Connect a key to comment.