The Problem It SolvesThe Problem It Solves
Running a full Bitcoin node requires downloading and validating the entire blockchain (~600 GB and growing). For mobile apps and lightweight wallets, this is impractical. The older solution (BIP 37/SPV) had serious flaws:
- Privacy leak: Clients send bloom filters matching their addresses to nodes
- DoS vulnerability: Malicious clients can overload nodes with expensive queries
- Trust required: Nodes can omit transactions without detection
Neutrino flips the model: instead of clients sending filters to servers, servers send filters to clients.
How Neutrino WorksHow Neutrino Works
The Three-Layer Sync ProcessThe Three-Layer Sync Process
┌─────────────────────────────────────────────────────────────┐
│ Step 1: Sync Block Headers (~40 MB) │
├─────────────────────────────────────────────────────────────┤
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │Block │→│Block │→│Block │→│Block │→ ... (500k+ blocks) │
│ │ 0 │ │ 1 │ │ 2 │ │ 3 │ │
│ └──────┘ └──────┘ └──────┘ └──────┘ │
│ 80 B 80 B 80 B 80 B │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Step 2: Sync Filter Headers (~20 MB) │
├─────────────────────────────────────────────────────────────┤
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │Filter│→│Filter│→│Filter│→│Filter│→ ... │
│ │Hdr 0 │ │Hdr 1 │ │Hdr 2 │ │Hdr 3 │ │
│ └──────┘ └──────┘ └──────┘ └──────┘ │
│ 32 B 32 B 32 B 32 B │
│ │
│ Checkpoints every 1,000 blocks for verification │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Step 3: Download Filters ON DEMAND (lazy loading) │
├─────────────────────────────────────────────────────────────┤
│ Only fetch filters for blocks you need to check │
│ Avg ~15 KB per filter (vs ~1-4 MB per block) │
│ 250x compression! │
└─────────────────────────────────────────────────────────────┘How Matching WorksHow Matching Works
Your Wallet Neutrino Client Full Node
───────────── ─────────────── ─────────
Addresses: New block arrives
- bc1q...abc ↓
- bc1q...xyz Download filter (~15 KB)
↓
Check locally:
Does filter match
ANY of my addresses?
↓
┌────┴────┐
│ NO │ Discard filter
│ MATCH │ Move to next block
└─────────┘
↓
┌────┴────┐
│ YES │ Download full → Request block
│ MATCH! │ block (~1-4 MB)
└─────────┘ ↓ ↓
Verify & extract ← Send block
your transactionsKey Privacy Win: Your addresses never leave your device. The node only knows you're interested in some block, not which addresses you're watching.
What's Inside a Filter?What's Inside a Filter?
Filters contain compressed representations of:
- All scriptPubKeys (output scripts) in the block
- All previous output scripts being spent (inputs)
Excluding:
- OP_RETURN outputs (to allow future commitments)
- Coinbase inputs (no previous output)
Filter Technology: Golomb-Coded Sets (GCS)Filter Technology: Golomb-Coded Sets (GCS)
Raw block data → Hash items → Sort → Calculate deltas → Compress
[addr1, addr2, ...] → [hash1, hash2, ...] → [h1, h2, h3...]
↓
[delta1, delta2, ...]
↓
Golomb-Rice encode
↓
~15 KB filter- Parameters: M=784,931, P=19
- False positive rate: ~1/784,931 per item
- Result: Probabilistic, but extremely efficient
Available OperationsAvailable Operations
1. Rescan1. Rescan
Scan the blockchain for relevant transactions.
rescan := neutrino.Rescan{
StartBlock: startHeight, // Where to begin
EndBlock: nil, // nil = scan to tip
WatchAddrs: []btcutil.Address{addr1, addr2},
WatchOutPoints: []wire.OutPoint{utxo1},
WatchTxIDs: []chainhash.Hash{txid1},
}How it works:
- Downloads filters for each block in range
- Checks if filter matches your watch list
- If match: downloads full block and extracts transactions
- Notifies you of matches via callback
Use case: Wallet recovery, checking balance for known addresses
2. GetUtxo2. GetUtxo
Check if a specific UTXO exists and is unspent.
report := chainService.GetUtxo(
outpoint, // The UTXO to check
startBlock, // CRITICAL: where to start searching
)How it works:
- Scans backwards from chain tip
- Downloads filters to check if UTXO appears
- Stops when found (spent or created)
- Returns either:
TxOut(if unspent) with scriptPubKey- Spending transaction details (if spent)
⚠️ Important: Always specify startBlock! Otherwise, if the UTXO doesn't exist, it will scan all the way back to block 1.
3. GetBlockFromNetwork3. GetBlockFromNetwork
Download a specific block by hash.
block := chainService.GetBlockFromNetwork(blockHash)Use case: After a filter matches, fetch the full block to extract transactions.
Resource RequirementsResource Requirements
StorageStorage
| Component | Size | Required | Notes |
| Block headers | ~40 MB | ✅ Always | 80 bytes × ~500k blocks |
| Filter headers | ~20 MB | ✅ Always | 32 bytes × ~500k blocks |
| Filters | ~7.5 GB total | ⚠️ Optional | Can discard after checking |
| Blocks | Variable | ❌ Never | Downloaded on-demand, not saved |
Minimal config: ~60 MB (headers only)
With filter cache: ~100-400 MB typical
If storing all filters: ~7.5 GB
Default in-memory caches:
- Filter cache: 30 MB (~1,450-2,300 filters)
- Block cache: 40 MB (temporary)
BandwidthBandwidth
Initial sync:
- Block headers: ~40 MB
- Filter headers: ~20 MB
- Total: ~60 MB
Ongoing:
- New block header: 80 bytes every ~10 min
- New filter header: 32 bytes every ~10 min
- New filter: ~15 KB every ~10 min (if checking)
- Full block: ~1-4 MB (only if filter matches)
Time from Cold StartTime from Cold Start
| Operation | Time |
| Initial header sync | ~5-10 minutes |
| Initial filter header sync | ~5-10 minutes |
| Total cold start | ~15-20 minutes |
| Rescan (recent 1000 blocks) | ~5-20 minutes |
| Check single UTXO | Seconds to minutes |
Times vary based on network conditions and peer availability
Key Limitations & ConsiderationsKey Limitations & Considerations
❌ You CANNOT query by address alone❌ You CANNOT query by address alone
You need the scriptPubKey (the actual locking script):
// ❌ Won't work
"bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"
// ✅ Need this
"0014 1111111254fb1a3bc28b3a3f4c5b1f5e1e1c5f6e"
(OP_0 + 20-byte hash for P2WPKH)Why? Filters contain scripts, not addresses. Different address types (P2PKH, P2WPKH, P2SH, etc.) have different scripts.
❌ No instant mempool monitoring❌ No instant mempool monitoring
Neutrino only notifies on confirmed transactions. For 0-conf detection, you need:
- Direct P2P connection monitoring (not standard Neutrino)
- Or connect to additional services
❌ No transaction history API❌ No transaction history API
Unlike block explorers, Neutrino doesn't give you:
- "All transactions for address X"
- Transaction history
- Current balance
You must:
- Know what addresses/UTXOs to watch
- Scan explicitly with Rescan or GetUtxo
- Build your own history database
⚠️ Privacy is good, not perfect⚠️ Privacy is good, not perfect
What's hidden:
- Your exact addresses
- Which transactions you care about
- Your wallet's full contents
What's leaked:
- You're interested in some specific blocks (when downloading)
- Timing patterns (when you check)
- The fact you're running a light client
To maximize privacy:
- Download blocks from random peers
- Use Tor
- Don't reuse addresses
- Consider downloading extra "dummy" blocks
⚠️ Trust model: Need at least ONE honest peer⚠️ Trust model: Need at least ONE honest peer
If ALL your connected peers are malicious and colluding:
- They could hide transactions from you
- They could provide invalid filters
Mitigations:
- Connect to multiple peers (default: 8 outbound)
- Filter headers have cryptographic commitments
- Clients detect conflicts and ban lying peers
- Compare results across peers
Practical ExamplesPractical Examples
Use Case 1: Light Wallet RecoveryUse Case 1: Light Wallet Recovery
Scenario: You have 3 addresses, need to find all transactions
1. Start Neutrino, wait for sync (~20 min)
2. Rescan from wallet creation date
3. For each block:
- Download filter (~15 KB)
- Check if any address matches
- If yes: download block (~2 MB)
- Extract your transactions
4. Build transaction history locallyCost: ~60 MB initial + variable for matching blocks
Use Case 2: Lightning Network PaymentUse Case 2: Lightning Network Payment
Scenario: Need to watch for channel force-close transaction
1. Already synced Neutrino client
2. GetUtxo for channel funding output
3. Checks backwards from tip:
- Is it still unspent? → Channel is open
- Was it spent? → Get spending tx details
4. React to channel stateCost: Minimal, just filter downloads until UTXO found
Use Case 3: Mobile WalletUse Case 3: Mobile Wallet
Scenario: iOS/Android app with minimal battery/data use
1. Sync headers at app launch (60 MB one-time)
2. Check filters for new blocks when app opens
3. Only download full blocks with your transactions
4. Discard filters after checking (minimal storage)Cost:
- Initial: 60 MB
- Ongoing: ~15 KB per 10 min + occasional full blocks
Comparison with AlternativesComparison with Alternatives
| Feature | Full Node | BIP 37 SPV | Neutrino | Block Explorer API |
| Storage | 600+ GB | < 1 GB | 60 MB - 8 GB | 0 GB |
| Initial sync | Days | Minutes | 15-20 min | Instant |
| Privacy | ⭐⭐⭐⭐⭐ | ⭐ | ⭐⭐⭐⭐ | ⭐ (none) |
| Trust | None | High | Low | Complete |
| Mempool | ✅ Yes | ✅ Yes | ❌ No | ✅ Yes |
| Address lookup | ✅ Any | ❌ Must watch | ❌ Must watch | ✅ Any |
| DoS resistance | N/A | ❌ Vulnerable | ✅ Resistant | N/A |
Quick ReferenceQuick Reference
What you need to provide:What you need to provide:
- For Rescan: Addresses (as scriptPubKeys), UTXOs, or TxIDs
- For GetUtxo: Outpoint (TxID + index) + recommended start block
- Filter key: First 16 bytes of block hash (automatic)
What you get back:What you get back:
- Rescan: Notifications of matching transactions in blocks
- GetUtxo: SpendReport with either:
- TxOut (if unspent)
- Spending transaction details (if spent)
- Nothing (if never existed in scanned range)
When to use Neutrino:When to use Neutrino:
✅ Mobile/embedded devices
✅ Privacy-conscious wallets
✅ Lightning Network clients
✅ Occasional blockchain queries
❌ High-frequency trading
❌ Real-time mempool monitoring
❌ Arbitrary address lookups
ConclusionConclusion
Neutrino represents a significant improvement over BIP 37 SPV:
Advantages:
- Much better privacy (addresses never leave device)
- DoS resistant (filters computed once)
- Efficient (~60 MB for full sync)
- Verifiable (cryptographic commitments)
Trade-offs:
- No instant mempool access
- Must know what to watch for
- 15-20 minute initial sync
- Requires scriptPubKey, not just address
For mobile Bitcoin and Lightning wallets that prioritize privacy and efficiency over real-time mempool monitoring, Neutrino is currently the best available light client protocol.