A high-performance, zero-copy Bitcoin blockchain parser and address clusterizer written in Rust.
BitIodine reads raw blk*.dat block files produced by bitcoind via memory mapping (mmap), parses blocks, headers, transactions, and script bytecodes, and provides an extensible Visitor pattern to analyze blockchain data, extract address balances, find OP_RETURN data outputs, and cluster co-spent addresses using Tarjan's Union-Find algorithm.
BitIodine is fully compatible with block files generated by all modern versions of Bitcoin Core (bitcoind v0.13 through v28.x+).
| Script Type | Standard | Address Prefix | Status |
|---|---|---|---|
| Pay-to-Pubkey (P2PK) | Legacy (Genesis) | Raw compressed / uncompressed pubkey | ✅ Supported |
| Pay-to-PubkeyHash (P2PKH) | Legacy | 1... (Base58) |
✅ Supported |
| Pay-to-ScriptHash (P2SH) | BIP-16 | 3... (Base58) |
✅ Supported |
| P2SH-wrapped SegWit (P2SH-P2WPKH / P2SH-P2WSH) | BIP-141 / BIP-143 | 3... (Base58) |
✅ Supported |
| Native SegWit v0 (P2WPKH) | BIP-141 / BIP-173 | bc1q... (Bech32) |
✅ Supported |
| Native SegWit v0 (P2WSH) | BIP-141 / BIP-173 | bc1q... (Bech32) |
✅ Supported |
| Taproot / SegWit v1 (P2TR) | BIP-341 / BIP-350 | bc1p... (Bech32m) |
✅ Supported |
| Future SegWit Witness Programs (v2–v16) | BIP-141 / BIP-350 | bc1... (Bech32m) |
✅ Supported |
Bare Multisig (OP_CHECKMULTISIG) |
Legacy / BIP-11 | M-of-N public keys | ✅ Supported |
Null Data (OP_RETURN) |
Core 0.9+ | Arbitrary payload data | ✅ Supported |
Relative Timelocks (OP_CSV) |
BIP-112 | OP_CHECKSEQUENCEVERIFY |
✅ Supported |
Absolute Timelocks (OP_CLTV) |
BIP-65 | OP_CHECKLOCKTIMEVERIFY |
✅ Supported |
- ⚡ Zero-Copy Memory-Mapped Parsing: Maps
blk*.datfiles directly into virtual memory for blazing-fast sequential scanning. - 👥 Address Clustering with CoinJoin Protection: Multi-input heuristic clustering with rank-based Union-Find (
DisjointSet), iterative path compression, and heuristic filtering for equal-denomination CoinJoin rounds. - 🧩 Pluggable Visitor Architecture: Clean
BlockChainVisitortrait to write custom analyzers, heuristics, and indexers. - 🛡️ Pure-Rust Cryptography: Uses standard RustCrypto ecosystem crates (
sha2,ripemd,bs58,hex) without legacy C dependencies. - 🖥️ CLI with Dynamic Action Selection: Run any visitor directly from the command line without recompiling.
To parse the blockchain with BitIodine, point the parser to an active or completed Bitcoin Core data directory:
- Unpruned Node Required:
bitcoindmust be running withprune=0(the default full-node setting).- Pruned nodes delete early
blk*.datfiles (blk00000.datonwards), preventing full historical analysis from Genesis.
- Default Block Paths:
- Linux:
~/.bitcoin/blocks/(or/var/lib/bitcoind/blocks/) - macOS:
~/Library/Application Support/Bitcoin/blocks/ - Windows:
%APPDATA%\Bitcoin\blocks\
- Linux:
Requires a stable Rust toolchain (1.75 or later).
cargo build --releaseThe optimized executable will be located at target/release/bitiodine.
A high-performance Bitcoin blockchain parser and address clusterizer in Rust.
Usage: bitiodine [OPTIONS]
Options:
-b, --blocks-dir <BLOCKS_DIR> Path to the bitcoind blocks directory [default: ~/.bitcoin/blocks]
-o, --output <OUTPUT> Path to the output file [default: clusters.csv]
-a, --action <ACTION> Action / visitor to run [default: clusterizer]
[possible values: clusterizer, dump-balances, dump-addresses,
dump-tx-hashes, dataoutput-finder, donation-finder, merkle]
-v... Sets the level of verbosity (-v for debug, -vv for trace)
-h, --help Print help
-V, --version Print version
| Action | Description | Output |
|---|---|---|
clusterizer (default) |
Groups together co-spent addresses into ownership clusters | CSV (<address>,<cluster_id>) |
dump-balances |
Calculates unspent balances per address | CSV (<balance_btc>,<hash160>,<address>) |
dump-addresses |
Extracts all unique recipient addresses across transactions | Stdout |
dump-tx-hashes |
Logs transaction IDs at regular block intervals | Stdout |
dataoutput-finder |
Discovers and prints OP_RETURN arbitrary payload messages |
Stdout |
donation-finder |
Identifies potential donation or non-standard outputs | Stdout |
merkle |
Computes and validates every block's Merkle root against its header | Stdout / Logs |
Run address clusterizer against default bitcoin directory:
./target/release/bitiodine -o clusters.csvDump address balances from a custom blocks path with debug logging:
./target/release/bitiodine -b /var/lib/bitcoind/blocks -a dump-balances -o balances.csv -vFind OP_RETURN data payloads:
./target/release/bitiodine -b /var/lib/bitcoind/blocks -a dataoutput-finderBitIodine is structured as a library (bitiodine) and a CLI application.
flowchart TD
subgraph Storage["1. Bitcoin Core Storage"]
BLK["blk*.dat Block Files<br/>128 MB Raw Disk Chunks"]
end
subgraph Core["2. BitIodine Zero-Copy Engine"]
MMAP["memmap2 Memory Mapping"]
SEQ["Block Sequencing & Orphan Reassembly<br/>In-memory skipped blocks table"]
FRAMER["Block & Header Framing<br/>80-byte header, Merkle root, timestamp"]
TXP["Transaction & Witness Deserializer<br/>txid and wtxid computation"]
SCRIPT["Script & Bytecode Engine<br/>P2PK, P2PKH, P2SH, SegWit v0-v16, Taproot, OP_RETURN"]
UTXO["OutputMap UTXO Tracker<br/>HashMap of Unspent Output Items"]
end
subgraph Visitors["3. Pluggable Visitor Engine (BlockChainVisitor)"]
CLUST["Clusterizer<br/>Union-Find DisjointSet with CoinJoin Filter"]
BAL["DumpBalances<br/>Balance Accounting Engine"]
ADDR["DumpAddresses<br/>Address Extractor"]
MERK["MerkleVisitor<br/>Header Merkle Root Verifier"]
DATA["DataOutputFinder<br/>OP_RETURN Payload Extractor"]
end
subgraph Sinks["4. Output Sinks"]
CSV["CSV Files<br/>clusters.csv / balances.csv"]
STDOUT["Stdout Stream"]
LOGS["Logger & Metrics"]
end
BLK --> MMAP
MMAP --> SEQ
SEQ --> FRAMER
FRAMER --> TXP
TXP --> SCRIPT
TXP <--> UTXO
TXP --> Visitors
CLUST --> CSV
BAL --> CSV
ADDR --> STDOUT
MERK --> LOGS
DATA --> STDOUT
sequenceDiagram
autonumber
participant BC as BlockChain Engine
participant V as BlockChainVisitor
participant UTXO as OutputMap (UTXO State)
Note over BC,V: For each block in sequential height order
BC->>V: visit_block_begin(block, height)
Note over BC,V: For each transaction in block
BC->>V: visit_transaction_begin(block_item)
loop For each Transaction Input
BC->>UTXO: Lookup & consume spent output (prev_hash, prev_index)
UTXO-->>BC: Some(spent_output_item)
BC->>V: visit_transaction_input(txin, block_item, tx_item, spent_output_item)
end
loop For each Transaction Output
BC->>V: visit_transaction_output(txout, block_item, tx_item)
V-->>BC: Option<OutputItem> (e.g. Address, Value)
BC->>UTXO: Store new unspent output item
end
BC->>V: visit_transaction_end(tx, block_item, tx_item)
BC->>V: visit_block_end(block, height, block_item)
Note over BC,V: When all block files have been processed
BC->>V: done()
flowchart LR
subgraph Inputs["Multi-Input Transaction"]
IN1["Address A"]
IN2["Address B"]
IN3["Address C"]
end
subgraph Filter["Heuristic Filters"]
CJ{"CoinJoin Check<br/>≥3 equal output amounts?"}
end
subgraph UnionFind["DisjointSet (Union-Find)"]
MAKE["make_set()"]
FIND["find() with 2-Pass<br/>Path Compression"]
UNION["union() by Rank"]
FIN["finalize()<br/>Canonical Roots"]
end
subgraph Output["Output Clusters"]
CLUSTER["Cluster Root Tag:<br/>{Address A, Address B, Address C}"]
end
Inputs --> CJ
CJ -- "Yes (Mixer)" --> SKIP["Skip Transaction<br/>(Preserve Independence)"]
CJ -- "No (Co-Spend)" --> MAKE
MAKE --> FIND
FIND --> UNION
UNION --> FIN
FIN --> CLUSTER
blockchain: Handles disk discovery, memory-mapping ofblk*.datfiles, block sequencing, and chain split/reorg resolution.block&header: Zero-copy block framing and 80-byte header deserialization (version, previous block hash, Merkle root, timestamp, bits, nonce).transactions&script: High-level Bitcoin transaction, input, output, and script bytecode interpreter (P2PK, P2PKH, P2SH, SegWit P2WPKH/P2WSH, Taproot P2TR, Multisig,OP_RETURN).hash&hash160: Type-safe, transparent wrappers for 256-bit and 160-bit Bitcoin hashes with standard little-endian formatting.visitors: Implementation of analysis plugins conforming toBlockChainVisitor.
Run the test suite:
cargo testRun clippy linter:
cargo clippy --all-targets -- -D warningsBitIodine is based on the pioneering academic research on Bitcoin transaction graph analysis and address clustering:
BitIodine: Extracting Intelligence from the Bitcoin Network
Michele Spagnuolo, Federico Maggi, and Stefano Zanero
Financial Cryptography and Data Security (FC 2014), Lecture Notes in Computer Science, vol 8437. Springer, Berlin, Heidelberg.
DOI: 10.1007/978-3-662-45472-5_29
If you use BitIodine in academic research, please cite the paper:
@inproceedings{spagnuolo2014bitiodine,
title = {{BitIodine}: Extracting Intelligence from the {Bitcoin} Network},
author = {Spagnuolo, Michele and Maggi, Federico and Zanero, Stefano},
booktitle = {Financial Cryptography and Data Security (FC)},
series = {Lecture Notes in Computer Science},
volume = {8437},
pages = {457--468},
year = {2014},
publisher = {Springer},
doi = {10.1007/978-3-662-45472-5_29}
}The blockchain parser architecture is based on research and code originally developed by Michele Spagnuolo (miki.it) and Mathias Svensson.